예제 #1
0
 async def setup(self):
     ''' Extend setup to also close the boot logger and, if desired,
     save the config.
     '''
     await super().setup()
     
     try:
         if self._save_cfg:
             user_id = self.account._user_id
             fingerprint = self.account._fingerprint
             
             logger.critical('Account created. Record these values in ' +
                             'case you need to log in to Hypergolix from ' +
                             'another machine, or in case your config ' +
                             'file is corrupted or lost:')
             logger.critical('User ID:\n' + user_id.as_str())
             logger.critical('Fingerprint:\n' + fingerprint.as_str())
             
             with Config.load(self._cfg_path) as config:
                 config.user.fingerprint = fingerprint
                 config.user.user_id = user_id
                 
     finally:
         logger.critical('Hypergolix boot complete.')
         self._boot_logger.stop()
예제 #2
0
 def test_full(self):
     ''' Test a full command chain for everything.
     '''
     with tempfile.TemporaryDirectory() as root:
         root = pathlib.Path(root)
         
         blank = Config(root / 'hypergolix.yml')
         blank.coerce_defaults()
         
         debug = Config(root / 'hypergolix.yml')
         debug.coerce_defaults()
         debug.instrumentation.debug = True
         
         nodebug = Config(root / 'hypergolix.yml')
         nodebug.coerce_defaults()
         nodebug.instrumentation.debug = False
         
         loud = Config(root / 'hypergolix.yml')
         loud.coerce_defaults()
         loud.instrumentation.verbosity = 'info'
         loud.instrumentation.debug = False
         
         normal = Config(root / 'hypergolix.yml')
         normal.coerce_defaults()
         normal.instrumentation.verbosity = 'warning'
         normal.instrumentation.debug = False
         
         host1 = Config(root / 'hypergolix.yml')
         host1.coerce_defaults()
         host1.remotes.append(Remote('host1', 123, True))
         host1.instrumentation.verbosity = 'warning'
         host1.instrumentation.debug = False
         
         host1hgx = Config(root / 'hypergolix.yml')
         host1hgx.coerce_defaults()
         host1hgx.remotes.append(Remote('host1', 123, True))
         host1hgx.remotes.append(Remote('hgx.hypergolix.com', 443, True))
         host1hgx.instrumentation.verbosity = 'warning'
         host1hgx.instrumentation.debug = False
         
         host1host2f = Config(root / 'hypergolix.yml')
         host1host2f.coerce_defaults()
         host1host2f.remotes.append(Remote('host1', 123, True))
         host1host2f.remotes.append(Remote('host2', 123, False))
         host1host2f.instrumentation.verbosity = 'warning'
         host1host2f.instrumentation.debug = False
         
         host1host2 = Config(root / 'hypergolix.yml')
         host1host2.coerce_defaults()
         host1host2.remotes.append(Remote('host1', 123, True))
         host1host2.remotes.append(Remote('host2', 123, True))
         host1host2.instrumentation.verbosity = 'warning'
         host1host2.instrumentation.debug = False
         
         # Definitely want to control the order of execution for this.
         valid_commands = [
             ('config', blank),
             ('config --whoami', blank)
         ]
         
         deprecated_commands = [
             ('config --debug', debug),
             ('config --no-debug', nodebug),
             ('config --verbosity loud', loud),
             ('config --verbosity normal', normal),
             ('config -ah host1 123 t', host1),
             ('config --addhost host1 123 t', host1),
             ('config -a hgx', host1hgx),
             ('config --add hgx', host1hgx),
             ('config -r hgx', host1),
             ('config --remove hgx', host1),
             # Note switch of TLS flag
             ('config -ah host2 123 f', host1host2f),
             # Note return of TLS flag
             ('config --addhost host2 123 t', host1host2),
             ('config -rh host2 123', host1),
             ('config --removehost host2 123', host1),
             ('config -o local', normal),
             ('config --only local', normal),
             ('config -ah host1 123 t -ah host2 123 t', host1host2),
         ]
         
         failing_commands = [
             'config -zz top',
             'config --verbosity XXXTREEEEEEEME',
             'config --debug --no-debug',
             'config -o local -a hgx',
         ]
         
         for cmd_str, cmd_result in valid_commands:
             with self.subTest(cmd_str):
                 cfg_path = root / 'hypergolix.yml'
                 # NOTE THAT THESE TESTS ARE CUMULATIVE! We definitely DON'T
                 # want to start with a fresh config each time around, or
                 # the tests will fail!
                 
                 argv = cmd_str.split()
                 argv.append('--root')
                 argv.append(str(cfg_path))
                 
                 with _NoSTDOUT():
                     ingest_args(argv)
                 
                 config = Config.load(cfg_path)
                 # THE PROBLEM HERE IS NOT JUST COERCE DEFAULTS! config.py,
                 # in its handle_args section, is passing in default values
                 # that are interfering with everything else.
                 self.assertEqual(config, cmd_result)
         
         for cmd_str, cmd_result in deprecated_commands:
             with self.subTest(cmd_str):
                 cfg_path = root / 'hypergolix.yml'
                 # NOTE THAT THESE TESTS ARE CUMULATIVE! We definitely DON'T
                 # want to start with a fresh config each time around, or
                 # the tests will fail!
                 
                 argv = cmd_str.split()
                 argv.append('--root')
                 argv.append(str(cfg_path))
                 
                 with _NoSTDOUT(), self.assertWarns(DeprecationWarning):
                     ingest_args(argv)
                 
                 config = Config.load(cfg_path)
                 # THE PROBLEM HERE IS NOT JUST COERCE DEFAULTS! config.py,
                 # in its handle_args section, is passing in default values
                 # that are interfering with everything else.
                 self.assertEqual(config, cmd_result)
             
     # Don't need the temp dir for this; un-context to escape permissions
     for cmd_str in failing_commands:
         with self.subTest(cmd_str):
             argv = cmd_str.split()
             argv.append('--root')
             argv.append(str(root))
             # Note that argparse will always push usage to stderr in a
             # suuuuuuper annoying way if we don't suppress it.
             with self.assertRaises(SystemExit), _NoSTDERR(), _NoSTDOUT():
                     ingest_args(argv)
예제 #3
0
def start(namespace=None):
    ''' Starts a Hypergolix daemon.
    '''
    # Command arg support is deprecated.
    if namespace is not None:
        # Gigantic error trap
        if ((namespace.host is not None) | (namespace.port is not None) |
            (namespace.debug is not None) | (namespace.traceur is not None) |
            (namespace.pidfile is not None) | (namespace.logdir is not None) |
            (namespace.cachedir is not None) | (namespace.chdir is not None) |
            (namespace.verbosity is not None)):
                raise RuntimeError('Server configuration through CLI is no ' +
                                   'longer supported. Edit hypergolix.yml ' +
                                   'configuration file instead.')
    
    with Daemonizer() as (is_setup, daemonizer):
        # Get our config path in setup, so that we error out before attempting
        # to daemonize (if anything is wrong).
        if is_setup:
            config = Config.find()
            config_path = config.path
            chdir = config_path.parent
            pid_file = config.server.pid_file
            
        else:
            config_path = None
            pid_file = None
            chdir = None
        
        # Daemonize.
        is_parent, config_path = daemonizer(
            str(pid_file),
            config_path,
            chdir = str(chdir),
            explicit_rescript = '-m hypergolix.service'
        )
        
        #####################
        # PARENT EXITS HERE #
        #####################
        
    config = Config.load(config_path)
    _ensure_dir_exists(config.server.ghidcache)
    _ensure_dir_exists(config.server.logdir)
    
    debug = _default_to(config.server.debug, False)
    verbosity = _default_to(config.server.verbosity, 'info')
    
    logutils.autoconfig(
        tofile = True,
        logdirname = config.server.logdir,
        logname = 'hgxserver',
        loglevel = verbosity
    )
        
    logger.debug('Parsing config...')
    host = _cast_host(config.server.host)
    rps = RemotePersistenceServer(
        config.server.ghidcache,
        host,
        config.server.port,
        reusable_loop = False,
        threaded = False,
        debug = debug
    )
    
    logger.debug('Starting health check...')
    # Start a health check
    healthcheck_server, healthcheck_thread = _serve_healthcheck()
    healthcheck_thread.start()
        
    logger.debug('Starting signal handler...')
    
    def signal_handler(signum):
        logger.info('Caught signal. Exiting.')
        healthcheck_server.shutdown()
        rps.stop_threadsafe_nowait()
        
    # Normally I'd do this within daemonization, but in this case, we need to
    # wait to have access to the handler.
    sighandler = SignalHandler1(
        str(config.server.pid_file),
        sigint = signal_handler,
        sigterm = signal_handler,
        sigabrt = signal_handler
    )
    sighandler.start()
    
    logger.info('Starting remote persistence server...')
    rps.start()
예제 #4
0
def run_daemon(cfg_path, pid_file, parent_port, account_entity,
               root_secret):
    ''' Start the actual Hypergolix daemon.
    '''
    # Start reporting to our parent about how stuff is going.
    parent_signaller = _StartupReporter(parent_port)
    startup_logger = parent_signaller.start()
    
    try:
        config = Config.load(cfg_path)
        # Convert paths to strs and make sure the dirs exist
        cache_dir = str(config.process.ghidcache)
        log_dir = str(config.process.logdir)
        _ensure_dir_exists(config.process.ghidcache)
        _ensure_dir_exists(config.process.logdir)
        
        debug = _default_to(config.instrumentation.debug, False)
        verbosity = _default_to(config.instrumentation.verbosity, 'info')
        
        logutils.autoconfig(
            tofile = True,
            logdirname = log_dir,
            loglevel = verbosity,
            logname = 'hgxapp'
        )
        
        ipc_port = config.process.ipc_port
        remotes = config.remotes
        # Look to see if we have an existing user_id to determine behavior
        save_cfg = not bool(config.user.user_id)
        
        hgxcore = _DaemonCore(
            cache_dir = cache_dir,
            ipc_port = ipc_port,
            reusable_loop = False,
            threaded = False,
            debug = debug,
            cfg_path = cfg_path,
            save_cfg = save_cfg,
            boot_logger = parent_signaller
        )
        
        for remote in remotes:
            hgxcore.add_remote(
                connection_cls = WSBeatingConn,
                host = remote.host,
                port = remote.port,
                tls = remote.tls
            )
        
        account = Account(
            user_id = account_entity,
            root_secret = root_secret,
            hgxcore = hgxcore
        )
        hgxcore.account = account
        
        # We need a signal handler for that.
        def signal_handler(signum):
            logger.info('Caught signal. Exiting.')
            hgxcore.stop_threadsafe_nowait()
            
        # Normally I'd do this within daemonization, but in this case, we need
        # to wait to have access to the handler.
        sighandler = SignalHandler1(
            pid_file,
            sigint = signal_handler,
            sigterm = signal_handler,
            sigabrt = signal_handler
        )
        sighandler.start()
        
        startup_logger.info('Booting Hypergolix...')
        hgxcore.start()
        
    except Exception:
        startup_logger.error('Failed to start Hypergolix:\n' +
                             ''.join(traceback.format_exc()))
        raise
        
    finally:
        # This is idempotent, so no worries if we already called it
        parent_signaller.stop()