blob: 62d3b5a01cdd1a769f04fc608f255a73fe2c1012 [file] [log] [blame]
Brad Bishopc342db32019-05-15 21:57:59 -04001#
Patrick Williams92b42cb2022-09-03 06:53:57 -05002# Copyright BitBake Contributors
3#
Brad Bishopc342db32019-05-15 21:57:59 -04004# SPDX-License-Identifier: GPL-2.0-only
5#
6
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007import os,sys,logging
8import signal, time
Patrick Williamsf1e5d692016-03-30 15:21:19 -05009import socket
Patrick Williamsc0f7c042017-02-23 20:41:17 -060010import io
Brad Bishop6e60e8b2018-02-01 10:27:11 -050011import sqlite3
Patrick Williamsc124f4f2015-09-15 14:41:29 -050012import prserv
13import prserv.db
14import errno
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050015import bb.asyncrpc
Patrick Williamsc124f4f2015-09-15 14:41:29 -050016
17logger = logging.getLogger("BitBake.PRserv")
18
Patrick Williamsc124f4f2015-09-15 14:41:29 -050019PIDPREFIX = "/tmp/PRServer_%s_%s.pid"
20singleton = None
21
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050022class PRServerClient(bb.asyncrpc.AsyncServerConnection):
Patrick Williamsac13d5f2023-11-24 18:59:46 -060023 def __init__(self, socket, table, read_only):
24 super().__init__(socket, 'PRSERVICE', logger)
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050025 self.handlers.update({
26 'get-pr': self.handle_get_pr,
27 'import-one': self.handle_import_one,
28 'export': self.handle_export,
29 'is-readonly': self.handle_is_readonly,
30 })
31 self.table = table
32 self.read_only = read_only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050033
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050034 def validate_proto_version(self):
35 return (self.proto_version == (1, 0))
36
37 async def dispatch_message(self, msg):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038 try:
Patrick Williamsac13d5f2023-11-24 18:59:46 -060039 return await super().dispatch_message(msg)
Andrew Geisslerc926e172021-05-07 16:11:35 -050040 except:
Andrew Geisslerc926e172021-05-07 16:11:35 -050041 self.table.sync()
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050042 raise
Patrick Williamsac13d5f2023-11-24 18:59:46 -060043 else:
44 self.table.sync_if_dirty()
Andrew Geisslerc926e172021-05-07 16:11:35 -050045
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050046 async def handle_get_pr(self, request):
47 version = request['version']
48 pkgarch = request['pkgarch']
49 checksum = request['checksum']
Andrew Geisslerc926e172021-05-07 16:11:35 -050050
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050051 response = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -050052 try:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050053 value = self.table.getValue(version, pkgarch, checksum)
54 response = {'value': value}
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055 except prserv.NotFoundError:
56 logger.error("can not find value for (%s, %s)",version, checksum)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050057 except sqlite3.Error as exc:
58 logger.error(str(exc))
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050059
Patrick Williamsac13d5f2023-11-24 18:59:46 -060060 return response
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050061
62 async def handle_import_one(self, request):
63 response = None
64 if not self.read_only:
65 version = request['version']
66 pkgarch = request['pkgarch']
67 checksum = request['checksum']
68 value = request['value']
69
70 value = self.table.importone(version, pkgarch, checksum, value)
71 if value is not None:
72 response = {'value': value}
73
Patrick Williamsac13d5f2023-11-24 18:59:46 -060074 return response
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050075
76 async def handle_export(self, request):
77 version = request['version']
78 pkgarch = request['pkgarch']
79 checksum = request['checksum']
80 colinfo = request['colinfo']
81
82 try:
83 (metainfo, datainfo) = self.table.export(version, pkgarch, checksum, colinfo)
84 except sqlite3.Error as exc:
85 logger.error(str(exc))
86 metainfo = datainfo = None
87
Patrick Williamsac13d5f2023-11-24 18:59:46 -060088 return {'metainfo': metainfo, 'datainfo': datainfo}
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050089
90 async def handle_is_readonly(self, request):
Patrick Williamsac13d5f2023-11-24 18:59:46 -060091 return {'readonly': self.read_only}
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050092
93class PRServer(bb.asyncrpc.AsyncServer):
94 def __init__(self, dbfile, read_only=False):
95 super().__init__(logger)
96 self.dbfile = dbfile
97 self.table = None
98 self.read_only = read_only
99
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600100 def accept_client(self, socket):
101 return PRServerClient(socket, self.table, self.read_only)
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500102
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600103 def start(self):
104 tasks = super().start()
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500105 self.db = prserv.db.PRData(self.dbfile, read_only=self.read_only)
106 self.table = self.db["PRMAIN"]
107
108 logger.info("Started PRServer with DBfile: %s, Address: %s, PID: %s" %
109 (self.dbfile, self.address, str(os.getpid())))
110
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600111 return tasks
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500112
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600113 async def stop(self):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500114 self.table.sync_if_dirty()
115 self.db.disconnect()
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600116 await super().stop()
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500117
118 def signal_handler(self):
119 super().signal_handler()
120 if self.table:
121 self.table.sync()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500122
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500123class PRServSingleton(object):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500124 def __init__(self, dbfile, logfile, host, port):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500125 self.dbfile = dbfile
126 self.logfile = logfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500127 self.host = host
128 self.port = port
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500129
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500130 def start(self):
131 self.prserv = PRServer(self.dbfile)
132 self.prserv.start_tcp_server(socket.gethostbyname(self.host), self.port)
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600133 self.process = self.prserv.serve_as_process(log_level=logging.WARNING)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500134
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500135 if not self.prserv.address:
136 raise PRServiceConfigError
137 if not self.port:
138 self.port = int(self.prserv.address.rsplit(':', 1)[1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500139
Andrew Geisslerc926e172021-05-07 16:11:35 -0500140def run_as_daemon(func, pidfile, logfile):
141 """
142 See Advanced Programming in the UNIX, Sec 13.3
143 """
144 try:
145 pid = os.fork()
146 if pid > 0:
147 os.waitpid(pid, 0)
148 #parent return instead of exit to give control
149 return pid
150 except OSError as e:
151 raise Exception("%s [%d]" % (e.strerror, e.errno))
152
153 os.setsid()
154 """
155 fork again to make sure the daemon is not session leader,
156 which prevents it from acquiring controlling terminal
157 """
158 try:
159 pid = os.fork()
160 if pid > 0: #parent
161 os._exit(0)
162 except OSError as e:
163 raise Exception("%s [%d]" % (e.strerror, e.errno))
164
165 os.chdir("/")
166
167 sys.stdout.flush()
168 sys.stderr.flush()
169
170 # We could be called from a python thread with io.StringIO as
171 # stdout/stderr or it could be 'real' unix fd forking where we need
172 # to physically close the fds to prevent the program launching us from
173 # potentially hanging on a pipe. Handle both cases.
174 si = open('/dev/null', 'r')
175 try:
176 os.dup2(si.fileno(),sys.stdin.fileno())
177 except (AttributeError, io.UnsupportedOperation):
178 sys.stdin = si
179 so = open(logfile, 'a+')
180 try:
181 os.dup2(so.fileno(),sys.stdout.fileno())
182 except (AttributeError, io.UnsupportedOperation):
183 sys.stdout = so
184 try:
185 os.dup2(so.fileno(),sys.stderr.fileno())
186 except (AttributeError, io.UnsupportedOperation):
187 sys.stderr = so
188
189 # Clear out all log handlers prior to the fork() to avoid calling
190 # event handlers not part of the PRserver
191 for logger_iter in logging.Logger.manager.loggerDict.keys():
192 logging.getLogger(logger_iter).handlers = []
193
194 # Ensure logging makes it to the logfile
195 streamhandler = logging.StreamHandler()
196 streamhandler.setLevel(logging.DEBUG)
197 formatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
198 streamhandler.setFormatter(formatter)
199 logger.addHandler(streamhandler)
200
201 # write pidfile
202 pid = str(os.getpid())
203 with open(pidfile, 'w') as pf:
204 pf.write("%s\n" % pid)
205
206 func()
207 os.remove(pidfile)
208 os._exit(0)
209
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500210def start_daemon(dbfile, host, port, logfile, read_only=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500211 ip = socket.gethostbyname(host)
212 pidfile = PIDPREFIX % (ip, port)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500213 try:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500214 with open(pidfile) as pf:
215 pid = int(pf.readline().strip())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500216 except IOError:
217 pid = None
218
219 if pid:
220 sys.stderr.write("pidfile %s already exist. Daemon already running?\n"
221 % pidfile)
222 return 1
223
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500224 dbfile = os.path.abspath(dbfile)
225 def daemon_main():
226 server = PRServer(dbfile, read_only=read_only)
227 server.start_tcp_server(ip, port)
228 server.serve_forever()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500229
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500230 run_as_daemon(daemon_main, pidfile, os.path.abspath(logfile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500231 return 0
232
233def stop_daemon(host, port):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500234 import glob
235 ip = socket.gethostbyname(host)
236 pidfile = PIDPREFIX % (ip, port)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500237 try:
Brad Bishop64c979e2019-11-04 13:55:29 -0500238 with open(pidfile) as pf:
239 pid = int(pf.readline().strip())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500240 except IOError:
241 pid = None
242
243 if not pid:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500244 # when server starts at port=0 (i.e. localhost:0), server actually takes another port,
245 # so at least advise the user which ports the corresponding server is listening
246 ports = []
247 portstr = ""
248 for pf in glob.glob(PIDPREFIX % (ip,'*')):
249 bn = os.path.basename(pf)
250 root, _ = os.path.splitext(bn)
251 ports.append(root.split('_')[-1])
252 if len(ports):
253 portstr = "Wrong port? Other ports listening at %s: %s" % (host, ' '.join(ports))
254
255 sys.stderr.write("pidfile %s does not exist. Daemon not running? %s\n"
256 % (pidfile,portstr))
257 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500258
259 try:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500260 if is_running(pid):
261 print("Sending SIGTERM to pr-server.")
262 os.kill(pid, signal.SIGTERM)
263 time.sleep(0.1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500264
Andrew Geisslerc926e172021-05-07 16:11:35 -0500265 if os.path.exists(pidfile):
266 os.remove(pidfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500267
268 except OSError as e:
269 err = str(e)
270 if err.find("No such process") <= 0:
271 raise e
272
273 return 0
274
275def is_running(pid):
276 try:
277 os.kill(pid, 0)
278 except OSError as err:
279 if err.errno == errno.ESRCH:
280 return False
281 return True
282
283def is_local_special(host, port):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500284 if (host == 'localhost' or host == '127.0.0.1') and not port:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500285 return True
286 else:
287 return False
288
289class PRServiceConfigError(Exception):
290 pass
291
292def auto_start(d):
293 global singleton
294
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500295 host_params = list(filter(None, (d.getVar('PRSERV_HOST') or '').split(':')))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500296 if not host_params:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500297 # Shutdown any existing PR Server
298 auto_shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500299 return None
300
301 if len(host_params) != 2:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500302 # Shutdown any existing PR Server
303 auto_shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500304 logger.critical('\n'.join(['PRSERV_HOST: incorrect format',
305 'Usage: PRSERV_HOST = "<hostname>:<port>"']))
306 raise PRServiceConfigError
307
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500308 host = host_params[0].strip().lower()
309 port = int(host_params[1])
310 if is_local_special(host, port):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500311 import bb.utils
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500312 cachedir = (d.getVar("PERSISTENT_DIR") or d.getVar("CACHE"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500313 if not cachedir:
314 logger.critical("Please set the 'PERSISTENT_DIR' or 'CACHE' variable")
315 raise PRServiceConfigError
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316 dbfile = os.path.join(cachedir, "prserv.sqlite3")
317 logfile = os.path.join(cachedir, "prserv.log")
Andrew Geissler82c905d2020-04-13 13:39:40 -0500318 if singleton:
319 if singleton.dbfile != dbfile:
320 # Shutdown any existing PR Server as doesn't match config
321 auto_shutdown()
322 if not singleton:
323 bb.utils.mkdirhier(cachedir)
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500324 singleton = PRServSingleton(os.path.abspath(dbfile), os.path.abspath(logfile), host, port)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500325 singleton.start()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326 if singleton:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500327 host = singleton.host
328 port = singleton.port
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500329
330 try:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500331 ping(host, port)
332 return str(host) + ":" + str(port)
333
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500334 except Exception:
335 logger.critical("PRservice %s:%d not available" % (host, port))
336 raise PRServiceConfigError
337
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500338def auto_shutdown():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500339 global singleton
Andrew Geisslerc926e172021-05-07 16:11:35 -0500340 if singleton and singleton.process:
341 singleton.process.terminate()
342 singleton.process.join()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500343 singleton = None
344
345def ping(host, port):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500346 from . import client
347
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600348 with client.PRClient() as conn:
349 conn.connect_tcp(host, port)
350 return conn.ping()
Andrew Geisslerc926e172021-05-07 16:11:35 -0500351
352def connect(host, port):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500353 from . import client
354
355 global singleton
356
357 if host.strip().lower() == 'localhost' and not port:
358 host = 'localhost'
359 port = singleton.port
360
361 conn = client.PRClient()
362 conn.connect_tcp(host, port)
363 return conn