mirror of
git://git.yoctoproject.org/poky
synced 2026-08-28 22:55:26 +00:00
* When using PRSERV_HOST = "localhost:0" inside
Docker container (tested with ubuntu 20.04 and 21.04) the
self.loop.run_until_complete never completed, so self.address
wasn't ever assigned few lines bellow and then
self.port = int(self.prserv.address.rsplit(':', 1)[1])
in lib/prserv/serv.py caused a bit ugly exception:
bitbake@599696cd20aa:~/nodistro/honister$ bitbake -k pkgconfig-native
Traceback (most recent call last):
File "/OE/nodistro/honister/bitbake/bin/bitbake", line 35, in <module>
sys.exit(bitbake_main(BitBakeConfigParameters(sys.argv),
File "/OE/nodistro/honister/bitbake/lib/bb/main.py", line 385, in bitbake_main
return ui_module.main(server_connection.connection, server_connection.events,
File "/OE/nodistro/honister/bitbake/lib/bb/ui/knotty.py", line 397, in main
params.updateToServer(server, os.environ.copy())
File "/OE/nodistro/honister/bitbake/lib/bb/cookerdata.py", line 75, in updateToServer
raise Exception("Unable to update the server configuration with local parameters: %s" % error)
Exception: Unable to update the server configuration with local parameters: Traceback (most recent call last):
File "/OE/nodistro/honister/bitbake/lib/bb/command.py", line 90, in runCommand
result = command_method(self, commandline)
File "/OE/nodistro/honister/bitbake/lib/bb/command.py", line 286, in updateConfig
command.cooker.updateConfigOpts(options, environment, cmdline)
File "/OE/nodistro/honister/bitbake/lib/bb/cooker.py", line 491, in updateConfigOpts
self.reset()
File "/OE/nodistro/honister/bitbake/lib/bb/cooker.py", line 1717, in reset
self.handlePRServ()
File "/OE/nodistro/honister/bitbake/lib/bb/cooker.py", line 383, in handlePRServ
self.prhost = prserv.serv.auto_start(self.data)
File "/OE/nodistro/honister/bitbake/lib/prserv/serv.py", line 318, in auto_start
singleton.start()
File "/OE/nodistro/honister/bitbake/lib/prserv/serv.py", line 133, in start
self.port = int(self.prserv.address.rsplit(':', 1)[1])
AttributeError: 'NoneType' object has no attribute 'rsplit'
* the issue was caused by "localhost" being resolved as IPv6 address ::1
and then asyncio failing to bind it, the same is reproducible with hashserv, but
hashserve at least shows nice error message:
bitbake$ bitbake-hashserv -l DEBUG -b localhost:0
Traceback (most recent call last):
File "/OE/nodistro/honister/bitbake/bin/bitbake-hashserv", line 59, in <module>
ret = main()
File "/OE/nodistro/honister/bitbake/bin/bitbake-hashserv", line 53, in main
server.serve_forever()
File "/OE/nodistro/honister/bitbake/lib/bb/asyncrpc/serv.py", line 233, in serve_forever
self.start()
File "/OE/nodistro/honister/bitbake/lib/bb/asyncrpc/serv.py", line 144, in start_tcp
self.server = self.loop.run_until_complete(server_coro)
File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "/usr/lib/python3.8/asyncio/streams.py", line 94, in start_server
return await loop.create_server(factory, host, port, **kwds)
File "/usr/lib/python3.8/asyncio/base_events.py", line 1463, in create_server
raise OSError(err.errno, 'error while attempting '
OSError: [Errno 99] error while attempting to bind on address ('::1', 0, 0, 0): cannot assign requested address
* or by bitbake-prserv in prserv.log:
bitbake$ bitbake-prserv --start --host=localhost --port=42005
bitbake$ cat prserv.log
Traceback (most recent call last):
File "/OE/nodistro/honister/bitbake/bin/bitbake-prserv", line 55, in <module>
ret = main()
File "/OE/nodistro/honister/bitbake/bin/bitbake-prserv", line 46, in main
ret=prserv.serv.start_daemon(options.dbfile, options.host, options.port,os.path.abspath(options.logfile), options.read_only)
File "/OE/nodistro/honister/bitbake/lib/prserv/serv.py", line 226, in start_daemon
run_as_daemon(daemon_main, pidfile, os.path.abspath(logfile))
File "/OE/nodistro/honister/bitbake/lib/prserv/serv.py", line 202, in run_as_daemon
func()
File "/OE/nodistro/honister/bitbake/lib/prserv/serv.py", line 224, in daemon_main
server.serve_forever()
File "/OE/nodistro/honister/bitbake/lib/bb/asyncrpc/serv.py", line 233, in serve_forever
self.start()
File "/OE/nodistro/honister/bitbake/lib/bb/asyncrpc/serv.py", line 144, in start_tcp
self.server = self.loop.run_until_complete(server_coro)
File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "/usr/lib/python3.8/asyncio/streams.py", line 94, in start_server
return await loop.create_server(factory, host, port, **kwds)
File "/usr/lib/python3.8/asyncio/base_events.py", line 1463, in create_server
raise OSError(err.errno, 'error while attempting '
OSError: [Errno 99] error while attempting to bind on address ('::1', 42005, 0, 0): cannot assign requested address
* while 127.0.0.1 works fine:
bitbake$ bitbake-prserv --start --host=127.0.0.1 --port=42005
bitbake$ cat prserv.log
DEBUG: Listening on ('127.0.0.1', 42005)
2021-08-26 22:28:05,828 Listening on ('127.0.0.1', 42005)
DEBUG: Opening PRServ database 'file:/OE/nodistro/honister/prserv.sqlite3'
2021-08-26 22:28:05,829 Opening PRServ database 'file:/OE/nodistro/honister/prserv.sqlite3'
NOTE: Started PRServer with DBfile: /OE/nodistro/honister/prserv.sqlite3, Address: 127.0.0.1:42005, PID: 39
2021-08-26 22:28:05,831 Started PRServer with DBfile: /OE/nodistro/honister/prserv.sqlite3, Address: 127.0.0.1:42005, PID: 39
but 127.0.0.1:0 wasn't handled as "autostart" like localhost:0 is
update is_local_special to allow that
* /etc/hosts file generated by docker contails localhost for both IPv4 and IPv6:
$ grep localhost /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
even when ipv6 is disabled in dockerd as reported in:
https://github.com/docker/for-linux/issues/250
* add a check for self.prserv.address to provide better error message:
ERROR: Unable to start PR Server, exitting
when something bad happens, but in this case you still need to read
bitbake-cookerdaemon.log to see the actuall error, in this case:
90 22:30:39.008441 --- Starting bitbake server pid 90 at 2021-08-26 22:30:39.008419 ---
90 22:30:39.023734 Started bitbake server pid 90
90 22:30:39.024286 Entering server connection loop
90 22:30:39.024753 Accepting [<socket.socket fd=6, family=AddressFamily.AF_UNIX, type=SocketKind.SOCK_STREAM, proto=0, laddr=bitbake.sock>] ([])
90 22:30:39.026314 Processing Client
90 22:30:39.026456 Connecting Client
90 22:30:39.027509 Running command ['setFeatures', [2]]
90 22:30:39.027757 Command Completed
90 22:30:39.028711 Running command ['updateConfig', {'abort': False, 'force': False, 'invalidate_stamp': None, 'dry_run': False, 'dump_signatures': [], 'extra_assume_provided': [], 'profile': False, 'prefile': [], 'postfile': [], 'server_timeout': None, 'nosetscene': False, 'setsceneonly': False, 'skipsetscene': False, 'runall': None, 'runonly': None, 'writeeventlog': None, 'build_verbose_shell': False, 'build_verbose_stdout': False, 'default_loglevel': 20, 'debug_domains': {}}, {'DISTRO': '', 'PWD': '/OE/nodistro/honister', 'HOME': '/OE', 'MACHINE': 'qemux86', 'BB_ENV_EXTRAWHITE': 'MACHINE DISTRO TCMODE TCLIBC http_proxy ftp_proxy https_proxy all_proxy ALL_PROXY no_proxy SSH_AGENT_PID SSH_AUTH_SOCK BB_SRCREV_POLICY SDKMACHINE BB_NUMBER_THREADS PARALLEL_MAKE GIT_PROXY_COMMAND GIT_PROXY_IGNORE SOCKS5_PASSWD SOCKS5_USER WEBOS_DISTRO_BUILD_ID PSEUDO_DISABLED PSEUDO_BUILD', 'PATH': '/OE/nodistro/honister/oe-core/scripts:/OE/nodistro/honister/bitbake/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', 'LC_ALL': 'en_US.UTF-8', 'MACHINES': 'qemux86', 'HOSTNAME': '6a439759e3c6', 'TOPDIR': '/OE/nodistro/honister', 'LANG': 'en_US.UTF-8', 'TERM': 'xterm', 'SHLVL': '1', 'BITBAKE_HOME': '/OE', 'BUILDDIR': '/OE/nodistro/honister/BUILD', 'OLDPWD': '/OE/nodistro/honister/bitbake', '_': '/OE/nodistro/honister/bitbake/bin/bitbake'}, ['/OE/nodistro/honister/bitbake/bin/bitbake', '-k', 'zlib-native']]
Process Process-1:
Traceback (most recent call last):
File "/usr/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
self.run()
File "/usr/lib/python3.8/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/OE/nodistro/honister/bitbake/lib/bb/asyncrpc/serv.py", line 255, in run
self.start()
File "/OE/nodistro/honister/bitbake/lib/bb/asyncrpc/serv.py", line 144, in start_tcp
self.server = self.loop.run_until_complete(server_coro)
File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "/usr/lib/python3.8/asyncio/streams.py", line 94, in start_server
return await loop.create_server(factory, host, port, **kwds)
File "/usr/lib/python3.8/asyncio/base_events.py", line 1463, in create_server
raise OSError(err.errno, 'error while attempting '
OSError: [Errno 99] error while attempting to bind on address ('::1', 0, 0, 0): cannot assign requested address
90 22:30:39.530037 Command Completed
90 22:30:39.530913 Processing Client
90 22:30:39.531023 Disconnecting Client
90 22:30:39.531638 No timeout, exiting.
90 22:30:39.632137 Exiting
90 22:30:39.637562 Original lockfile contents: ['90\n']
90 22:30:39.638107 Exiting as we could obtain the lock
(Bitbake rev: c2cdda0e5fc720c60d3b8537fc702cb118981bd2)
Signed-off-by: Martin Jansa <Martin.Jansa@gmail.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
361 lines
10 KiB
Python
361 lines
10 KiB
Python
#
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
#
|
|
|
|
import os,sys,logging
|
|
import signal, time
|
|
import socket
|
|
import io
|
|
import sqlite3
|
|
import prserv
|
|
import prserv.db
|
|
import errno
|
|
import bb.asyncrpc
|
|
|
|
logger = logging.getLogger("BitBake.PRserv")
|
|
|
|
PIDPREFIX = "/tmp/PRServer_%s_%s.pid"
|
|
singleton = None
|
|
|
|
class PRServerClient(bb.asyncrpc.AsyncServerConnection):
|
|
def __init__(self, reader, writer, table, read_only):
|
|
super().__init__(reader, writer, 'PRSERVICE', logger)
|
|
self.handlers.update({
|
|
'get-pr': self.handle_get_pr,
|
|
'import-one': self.handle_import_one,
|
|
'export': self.handle_export,
|
|
'is-readonly': self.handle_is_readonly,
|
|
})
|
|
self.table = table
|
|
self.read_only = read_only
|
|
|
|
def validate_proto_version(self):
|
|
return (self.proto_version == (1, 0))
|
|
|
|
async def dispatch_message(self, msg):
|
|
try:
|
|
await super().dispatch_message(msg)
|
|
except:
|
|
self.table.sync()
|
|
raise
|
|
|
|
self.table.sync_if_dirty()
|
|
|
|
async def handle_get_pr(self, request):
|
|
version = request['version']
|
|
pkgarch = request['pkgarch']
|
|
checksum = request['checksum']
|
|
|
|
response = None
|
|
try:
|
|
value = self.table.getValue(version, pkgarch, checksum)
|
|
response = {'value': value}
|
|
except prserv.NotFoundError:
|
|
logger.error("can not find value for (%s, %s)",version, checksum)
|
|
except sqlite3.Error as exc:
|
|
logger.error(str(exc))
|
|
|
|
self.write_message(response)
|
|
|
|
async def handle_import_one(self, request):
|
|
response = None
|
|
if not self.read_only:
|
|
version = request['version']
|
|
pkgarch = request['pkgarch']
|
|
checksum = request['checksum']
|
|
value = request['value']
|
|
|
|
value = self.table.importone(version, pkgarch, checksum, value)
|
|
if value is not None:
|
|
response = {'value': value}
|
|
|
|
self.write_message(response)
|
|
|
|
async def handle_export(self, request):
|
|
version = request['version']
|
|
pkgarch = request['pkgarch']
|
|
checksum = request['checksum']
|
|
colinfo = request['colinfo']
|
|
|
|
try:
|
|
(metainfo, datainfo) = self.table.export(version, pkgarch, checksum, colinfo)
|
|
except sqlite3.Error as exc:
|
|
logger.error(str(exc))
|
|
metainfo = datainfo = None
|
|
|
|
response = {'metainfo': metainfo, 'datainfo': datainfo}
|
|
self.write_message(response)
|
|
|
|
async def handle_is_readonly(self, request):
|
|
response = {'readonly': self.read_only}
|
|
self.write_message(response)
|
|
|
|
class PRServer(bb.asyncrpc.AsyncServer):
|
|
def __init__(self, dbfile, read_only=False):
|
|
super().__init__(logger)
|
|
self.dbfile = dbfile
|
|
self.table = None
|
|
self.read_only = read_only
|
|
|
|
def accept_client(self, reader, writer):
|
|
return PRServerClient(reader, writer, self.table, self.read_only)
|
|
|
|
def _serve_forever(self):
|
|
self.db = prserv.db.PRData(self.dbfile, read_only=self.read_only)
|
|
self.table = self.db["PRMAIN"]
|
|
|
|
logger.info("Started PRServer with DBfile: %s, Address: %s, PID: %s" %
|
|
(self.dbfile, self.address, str(os.getpid())))
|
|
|
|
super()._serve_forever()
|
|
|
|
self.table.sync_if_dirty()
|
|
self.db.disconnect()
|
|
|
|
def signal_handler(self):
|
|
super().signal_handler()
|
|
if self.table:
|
|
self.table.sync()
|
|
|
|
class PRServSingleton(object):
|
|
def __init__(self, dbfile, logfile, host, port):
|
|
self.dbfile = dbfile
|
|
self.logfile = logfile
|
|
self.host = host
|
|
self.port = port
|
|
|
|
def start(self):
|
|
self.prserv = PRServer(self.dbfile)
|
|
self.prserv.start_tcp_server(self.host, self.port)
|
|
self.process = self.prserv.serve_as_process()
|
|
|
|
if not self.prserv.address:
|
|
raise PRServiceConfigError
|
|
if not self.port:
|
|
self.port = int(self.prserv.address.rsplit(':', 1)[1])
|
|
|
|
def run_as_daemon(func, pidfile, logfile):
|
|
"""
|
|
See Advanced Programming in the UNIX, Sec 13.3
|
|
"""
|
|
try:
|
|
pid = os.fork()
|
|
if pid > 0:
|
|
os.waitpid(pid, 0)
|
|
#parent return instead of exit to give control
|
|
return pid
|
|
except OSError as e:
|
|
raise Exception("%s [%d]" % (e.strerror, e.errno))
|
|
|
|
os.setsid()
|
|
"""
|
|
fork again to make sure the daemon is not session leader,
|
|
which prevents it from acquiring controlling terminal
|
|
"""
|
|
try:
|
|
pid = os.fork()
|
|
if pid > 0: #parent
|
|
os._exit(0)
|
|
except OSError as e:
|
|
raise Exception("%s [%d]" % (e.strerror, e.errno))
|
|
|
|
os.chdir("/")
|
|
|
|
sys.stdout.flush()
|
|
sys.stderr.flush()
|
|
|
|
# We could be called from a python thread with io.StringIO as
|
|
# stdout/stderr or it could be 'real' unix fd forking where we need
|
|
# to physically close the fds to prevent the program launching us from
|
|
# potentially hanging on a pipe. Handle both cases.
|
|
si = open('/dev/null', 'r')
|
|
try:
|
|
os.dup2(si.fileno(),sys.stdin.fileno())
|
|
except (AttributeError, io.UnsupportedOperation):
|
|
sys.stdin = si
|
|
so = open(logfile, 'a+')
|
|
try:
|
|
os.dup2(so.fileno(),sys.stdout.fileno())
|
|
except (AttributeError, io.UnsupportedOperation):
|
|
sys.stdout = so
|
|
try:
|
|
os.dup2(so.fileno(),sys.stderr.fileno())
|
|
except (AttributeError, io.UnsupportedOperation):
|
|
sys.stderr = so
|
|
|
|
# Clear out all log handlers prior to the fork() to avoid calling
|
|
# event handlers not part of the PRserver
|
|
for logger_iter in logging.Logger.manager.loggerDict.keys():
|
|
logging.getLogger(logger_iter).handlers = []
|
|
|
|
# Ensure logging makes it to the logfile
|
|
streamhandler = logging.StreamHandler()
|
|
streamhandler.setLevel(logging.DEBUG)
|
|
formatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
|
|
streamhandler.setFormatter(formatter)
|
|
logger.addHandler(streamhandler)
|
|
|
|
# write pidfile
|
|
pid = str(os.getpid())
|
|
with open(pidfile, 'w') as pf:
|
|
pf.write("%s\n" % pid)
|
|
|
|
func()
|
|
os.remove(pidfile)
|
|
os._exit(0)
|
|
|
|
def start_daemon(dbfile, host, port, logfile, read_only=False):
|
|
ip = socket.gethostbyname(host)
|
|
pidfile = PIDPREFIX % (ip, port)
|
|
try:
|
|
with open(pidfile) as pf:
|
|
pid = int(pf.readline().strip())
|
|
except IOError:
|
|
pid = None
|
|
|
|
if pid:
|
|
sys.stderr.write("pidfile %s already exist. Daemon already running?\n"
|
|
% pidfile)
|
|
return 1
|
|
|
|
dbfile = os.path.abspath(dbfile)
|
|
def daemon_main():
|
|
server = PRServer(dbfile, read_only=read_only)
|
|
server.start_tcp_server(host, port)
|
|
server.serve_forever()
|
|
|
|
run_as_daemon(daemon_main, pidfile, os.path.abspath(logfile))
|
|
return 0
|
|
|
|
def stop_daemon(host, port):
|
|
import glob
|
|
ip = socket.gethostbyname(host)
|
|
pidfile = PIDPREFIX % (ip, port)
|
|
try:
|
|
with open(pidfile) as pf:
|
|
pid = int(pf.readline().strip())
|
|
except IOError:
|
|
pid = None
|
|
|
|
if not pid:
|
|
# when server starts at port=0 (i.e. localhost:0), server actually takes another port,
|
|
# so at least advise the user which ports the corresponding server is listening
|
|
ports = []
|
|
portstr = ""
|
|
for pf in glob.glob(PIDPREFIX % (ip,'*')):
|
|
bn = os.path.basename(pf)
|
|
root, _ = os.path.splitext(bn)
|
|
ports.append(root.split('_')[-1])
|
|
if len(ports):
|
|
portstr = "Wrong port? Other ports listening at %s: %s" % (host, ' '.join(ports))
|
|
|
|
sys.stderr.write("pidfile %s does not exist. Daemon not running? %s\n"
|
|
% (pidfile,portstr))
|
|
return 1
|
|
|
|
try:
|
|
if is_running(pid):
|
|
print("Sending SIGTERM to pr-server.")
|
|
os.kill(pid, signal.SIGTERM)
|
|
time.sleep(0.1)
|
|
|
|
if os.path.exists(pidfile):
|
|
os.remove(pidfile)
|
|
|
|
except OSError as e:
|
|
err = str(e)
|
|
if err.find("No such process") <= 0:
|
|
raise e
|
|
|
|
return 0
|
|
|
|
def is_running(pid):
|
|
try:
|
|
os.kill(pid, 0)
|
|
except OSError as err:
|
|
if err.errno == errno.ESRCH:
|
|
return False
|
|
return True
|
|
|
|
def is_local_special(host, port):
|
|
if (host == 'localhost' or host == '127.0.0.1') and not port:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
class PRServiceConfigError(Exception):
|
|
pass
|
|
|
|
def auto_start(d):
|
|
global singleton
|
|
|
|
host_params = list(filter(None, (d.getVar('PRSERV_HOST') or '').split(':')))
|
|
if not host_params:
|
|
# Shutdown any existing PR Server
|
|
auto_shutdown()
|
|
return None
|
|
|
|
if len(host_params) != 2:
|
|
# Shutdown any existing PR Server
|
|
auto_shutdown()
|
|
logger.critical('\n'.join(['PRSERV_HOST: incorrect format',
|
|
'Usage: PRSERV_HOST = "<hostname>:<port>"']))
|
|
raise PRServiceConfigError
|
|
|
|
host = host_params[0].strip().lower()
|
|
port = int(host_params[1])
|
|
if is_local_special(host, port):
|
|
import bb.utils
|
|
cachedir = (d.getVar("PERSISTENT_DIR") or d.getVar("CACHE"))
|
|
if not cachedir:
|
|
logger.critical("Please set the 'PERSISTENT_DIR' or 'CACHE' variable")
|
|
raise PRServiceConfigError
|
|
dbfile = os.path.join(cachedir, "prserv.sqlite3")
|
|
logfile = os.path.join(cachedir, "prserv.log")
|
|
if singleton:
|
|
if singleton.dbfile != dbfile:
|
|
# Shutdown any existing PR Server as doesn't match config
|
|
auto_shutdown()
|
|
if not singleton:
|
|
bb.utils.mkdirhier(cachedir)
|
|
singleton = PRServSingleton(os.path.abspath(dbfile), os.path.abspath(logfile), host, port)
|
|
singleton.start()
|
|
if singleton:
|
|
host = singleton.host
|
|
port = singleton.port
|
|
|
|
try:
|
|
ping(host, port)
|
|
return str(host) + ":" + str(port)
|
|
|
|
except Exception:
|
|
logger.critical("PRservice %s:%d not available" % (host, port))
|
|
raise PRServiceConfigError
|
|
|
|
def auto_shutdown():
|
|
global singleton
|
|
if singleton and singleton.process:
|
|
singleton.process.terminate()
|
|
singleton.process.join()
|
|
singleton = None
|
|
|
|
def ping(host, port):
|
|
from . import client
|
|
|
|
conn = client.PRClient()
|
|
conn.connect_tcp(host, port)
|
|
return conn.ping()
|
|
|
|
def connect(host, port):
|
|
from . import client
|
|
|
|
global singleton
|
|
|
|
if host.strip().lower() == 'localhost' and not port:
|
|
host = 'localhost'
|
|
port = singleton.port
|
|
|
|
conn = client.PRClient()
|
|
conn.connect_tcp(host, port)
|
|
return conn
|