blob: 5fc8863f70a91c5b2675a200bb5255d2efc907b2 [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
Patrick Williamsb58112e2024-03-07 11:16:36 -0600265 try:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500266 os.remove(pidfile)
Patrick Williamsb58112e2024-03-07 11:16:36 -0600267 except FileNotFoundError:
268 # The PID file might have been removed by the exiting process
269 pass
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500270
271 except OSError as e:
272 err = str(e)
273 if err.find("No such process") <= 0:
274 raise e
275
276 return 0
277
278def is_running(pid):
279 try:
280 os.kill(pid, 0)
281 except OSError as err:
282 if err.errno == errno.ESRCH:
283 return False
284 return True
285
286def is_local_special(host, port):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500287 if (host == 'localhost' or host == '127.0.0.1') and not port:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500288 return True
289 else:
290 return False
291
292class PRServiceConfigError(Exception):
293 pass
294
295def auto_start(d):
296 global singleton
297
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500298 host_params = list(filter(None, (d.getVar('PRSERV_HOST') or '').split(':')))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500299 if not host_params:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500300 # Shutdown any existing PR Server
301 auto_shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500302 return None
303
304 if len(host_params) != 2:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500305 # Shutdown any existing PR Server
306 auto_shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500307 logger.critical('\n'.join(['PRSERV_HOST: incorrect format',
308 'Usage: PRSERV_HOST = "<hostname>:<port>"']))
309 raise PRServiceConfigError
310
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500311 host = host_params[0].strip().lower()
312 port = int(host_params[1])
313 if is_local_special(host, port):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500314 import bb.utils
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500315 cachedir = (d.getVar("PERSISTENT_DIR") or d.getVar("CACHE"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316 if not cachedir:
317 logger.critical("Please set the 'PERSISTENT_DIR' or 'CACHE' variable")
318 raise PRServiceConfigError
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319 dbfile = os.path.join(cachedir, "prserv.sqlite3")
320 logfile = os.path.join(cachedir, "prserv.log")
Andrew Geissler82c905d2020-04-13 13:39:40 -0500321 if singleton:
322 if singleton.dbfile != dbfile:
323 # Shutdown any existing PR Server as doesn't match config
324 auto_shutdown()
325 if not singleton:
326 bb.utils.mkdirhier(cachedir)
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500327 singleton = PRServSingleton(os.path.abspath(dbfile), os.path.abspath(logfile), host, port)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500328 singleton.start()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500329 if singleton:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500330 host = singleton.host
331 port = singleton.port
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500332
333 try:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500334 ping(host, port)
335 return str(host) + ":" + str(port)
336
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500337 except Exception:
338 logger.critical("PRservice %s:%d not available" % (host, port))
339 raise PRServiceConfigError
340
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500341def auto_shutdown():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500342 global singleton
Andrew Geisslerc926e172021-05-07 16:11:35 -0500343 if singleton and singleton.process:
344 singleton.process.terminate()
345 singleton.process.join()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500346 singleton = None
347
348def ping(host, port):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500349 from . import client
350
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600351 with client.PRClient() as conn:
352 conn.connect_tcp(host, port)
353 return conn.ping()
Andrew Geisslerc926e172021-05-07 16:11:35 -0500354
355def connect(host, port):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500356 from . import client
357
358 global singleton
359
360 if host.strip().lower() == 'localhost' and not port:
361 host = 'localhost'
362 port = singleton.port
363
364 conn = client.PRClient()
365 conn.connect_tcp(host, port)
366 return conn