import Pyro4 import logging Pyro4.config.REQUIRE_EXPOSE = True log = logging.getLogger(__name__) def main(): opts = getopts() from . import register_deserialization_handlers register_deserialization_handlers() log_forwarder = setup_logging() server = Server(opts['--listen'], log_forwarder) server.start() def setup_logging(): root = logging.getLogger() root.setLevel(logging.NOTSET) from .log import LogForwarder log_forwarder = LogForwarder() root.addHandler(log_forwarder) from datetime import datetime import os.path from bootstrapvz.base.log import get_file_handler timestamp = datetime.now().strftime('%Y%m%d%H%M%S') filename = '{timestamp}_remote.log'.format(timestamp=timestamp) logfile_path = os.path.join('/var/log/bootstrap-vz', filename) file_handler = get_file_handler(logfile_path, True) root.addHandler(file_handler) return log_forwarder def getopts(): from docopt import docopt usage = """bootstrap-vz-server Usage: bootstrap-vz-server [options] Options: --listen Serve on specified port [default: 46675] -h, --help show this help """ return docopt(usage) class Server(object): def __init__(self, listen_port, log_forwarder): self.stop_serving = False self.log_forwarder = log_forwarder self.listen_port = listen_port def start(self): Pyro4.config.COMMTIMEOUT = 0.5 daemon = Pyro4.Daemon('localhost', port=int(self.listen_port), unixsocket=None) daemon.register(self, 'server') daemon.requestLoop(loopCondition=lambda: not self.stop_serving) @Pyro4.expose def set_callback_server(self, server): log.debug('Forwarding logs to the callback server') self.log_forwarder.set_server(server) @Pyro4.expose def ping(self): if hasattr(self, 'connection_timeout'): # pylint: disable=no-member self.connection_timeout.cancel() del self.connection_timeout return 'pong' @Pyro4.expose def stop(self): if hasattr(self, 'bootstrap_process'): log.warn('Sending SIGINT to bootstrapping process') import os import signal os.killpg(self.bootstrap_process.pid, signal.SIGINT) self.bootstrap_process.join() # We can't send a SIGINT to the server, # for some reason the Pyro4 shutdowns are rather unclean, # throwing exceptions and such. self.stop_serving = True @Pyro4.expose def run(self, manifest, debug=False, dry_run=False): def bootstrap(queue): # setsid() creates a new session, making this process the group leader. # We do that, so when the server calls killpg (kill process group) # on us, it won't kill itself (this process was spawned from a # thread under the server, meaning it's part of the same group). # The process hierarchy looks like this: # Pyro server (process - listening on a port) # +- pool thread # +- pool thread # +- pool thread # +- started thread (the one that got the "run()" call) # L bootstrap() process (us) # Calling setsid() also fixes another problem: # SIGINTs sent to this process seem to be redirected # to the process leader. Since there is a thread between # us and the process leader, the signal will not be propagated # (signals are not propagated to threads), this means that any # subprocess we start (i.e. debootstrap) will not get a SIGINT. import os os.setsid() from bootstrapvz.base.main import run try: bootstrap_info = run(manifest, debug=debug, dry_run=dry_run) queue.put(bootstrap_info) except (Exception, KeyboardInterrupt) as e: # pylint: disable=broad-except queue.put(e) from multiprocessing import Queue from multiprocessing import Process queue = Queue() self.bootstrap_process = Process(target=bootstrap, args=(queue,)) self.bootstrap_process.start() self.bootstrap_process.join() del self.bootstrap_process result = queue.get() if isinstance(result, Exception): raise result return result