def start_volttron_process(opts):
    '''Start the main volttron process.

    Typically this function is used from main.py and just uses the argparser's
    Options arguments as inputs.   It also can be called with a dictionary.  In
    that case the dictionaries keys are mapped into a value that acts like the
    args options.
    '''

    if isinstance(opts, dict):
        opts = type('Options', (), opts)()
        # vip_address is meant to be a list so make it so.
        if not isinstance(opts.vip_address, list):
            opts.vip_address = [opts.vip_address]
    if opts.log:
        opts.log = config.expandall(opts.log)
    if opts.log_config:
        opts.log_config = config.expandall(opts.log_config)

    # Configure logging
    level = max(1, opts.verboseness)
    if opts.monitor and level > logging.INFO:
        level = logging.INFO

    if opts.log is None:
        log_to_file(sys.stderr, level)
    elif opts.log == '-':
        log_to_file(sys.stdout, level)
    elif opts.log:
        log_to_file(opts.log, level, handler_class=handlers.WatchedFileHandler)
    else:
        log_to_file(None, 100, handler_class=lambda x: logging.NullHandler())

    if opts.log_config:
        with open(opts.log_config, 'r') as f:
            for line in f.readlines():
                _log.info(line.rstrip())

        error = configure_logging(opts.log_config)

        if error:
            _log.error('{}: {}'.format(*error))
            sys.exit(1)

    opts.publish_address = config.expandall(opts.publish_address)
    opts.subscribe_address = config.expandall(opts.subscribe_address)
    opts.vip_address = [config.expandall(addr) for addr in opts.vip_address]
    opts.vip_local_address = config.expandall(opts.vip_local_address)

    if opts.instance_name is None:
        if len(opts.vip_address) > 0:
            opts.instance_name = opts.vip_address[0]
    import urlparse

    if opts.bind_web_address:
        parsed = urlparse.urlparse(opts.bind_web_address)
        if parsed.scheme not in ('http', 'https'):
            raise StandardError(
                'bind-web-address must begin with http or https.')
        opts.bind_web_address = config.expandall(opts.bind_web_address)
    if opts.volttron_central_address:
        parsed = urlparse.urlparse(opts.volttron_central_address)
        if parsed.scheme not in ('http', 'https', 'tcp'):
            raise StandardError(
                'volttron-central-address must begin with tcp, http or https.')
        opts.volttron_central_address = config.expandall(
            opts.volttron_central_address)
    opts.volttron_central_serverkey = opts.volttron_central_serverkey

    # Log configuration options
    if getattr(opts, 'show_config', False):
        _log.info('volttron version: {}'.format(__version__))
        for name, value in sorted(vars(opts).iteritems()):
            _log.info("%s: %s" % (name, str(repr(value))))

    # Increase open files resource limit to max or 8192 if unlimited
    try:
        soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)

    except OSError:
        _log.exception('error getting open file limits')
    else:
        if soft != hard and soft != resource.RLIM_INFINITY:
            try:
                limit = 8192 if hard == resource.RLIM_INFINITY else hard
                resource.setrlimit(resource.RLIMIT_NOFILE, (limit, hard))
            except OSError:
                _log.exception('error setting open file limits')
            else:
                _log.debug('open file resource limit increased from %d to %d',
                           soft, limit)
        _log.debug('open file resource limit %d to %d', soft, hard)
    # Set configuration
    if HAVE_RESTRICTED:
        if opts.verify_agents:
            _log.info('Agent integrity verification enabled')
        if opts.resource_monitor:
            _log.info('Resource monitor enabled')
            opts.resmon = resmon.ResourceMonitor()
    opts.aip = aip.AIPplatform(opts)
    opts.aip.setup()

    # Check for secure mode/permissions on VOLTTRON_HOME directory
    mode = os.stat(opts.volttron_home).st_mode
    if mode & (stat.S_IWGRP | stat.S_IWOTH):
        _log.warning('insecure mode on directory: %s', opts.volttron_home)
    # Get or generate encryption key
    keystore = KeyStore()
    _log.debug('using key-store file %s', keystore.filename)
    if not keystore.isvalid():
        _log.warning('key store is invalid; connections may fail')
    st = os.stat(keystore.filename)
    if st.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
        _log.warning('insecure mode on key file')
    publickey = decode_key(keystore.public)
    if publickey:
        _log.info('public key: %s', encode_key(publickey))
        # Authorize the platform key:
        entry = AuthEntry(credentials=encode_key(publickey),
                          user_id='platform',
                          comments='Automatically added by platform on start')
        AuthFile().add(entry, overwrite=True)
        # Add platform key to known-hosts file:
        known_hosts = KnownHostsStore()
        known_hosts.add(opts.vip_local_address, encode_key(publickey))
        for addr in opts.vip_address:
            known_hosts.add(addr, encode_key(publickey))
    secretkey = decode_key(keystore.secret)

    # The following line doesn't appear to do anything, but it creates
    # a context common to the green and non-green zmq modules.
    zmq.Context.instance()  # DO NOT REMOVE LINE!!
    # zmq.Context.instance().set(zmq.MAX_SOCKETS, 2046)

    tracker = Tracker()
    protected_topics_file = os.path.join(opts.volttron_home,
                                         'protected_topics.json')
    _log.debug('protected topics file %s', protected_topics_file)
    external_address_file = os.path.join(opts.volttron_home,
                                         'external_address.json')
    _log.debug('external_address_file file %s', external_address_file)
    protected_topics = {}

    # Main loops
    def router(stop):
        try:
            Router(opts.vip_local_address,
                   opts.vip_address,
                   secretkey=secretkey,
                   publickey=publickey,
                   default_user_id=b'vip.service',
                   monitor=opts.monitor,
                   tracker=tracker,
                   volttron_central_address=opts.volttron_central_address,
                   volttron_central_serverkey=opts.volttron_central_serverkey,
                   instance_name=opts.instance_name,
                   bind_web_address=opts.bind_web_address,
                   protected_topics=protected_topics,
                   external_address_file=external_address_file,
                   msgdebug=opts.msgdebug).run()
        except Exception:
            _log.exception('Unhandled exception in router loop')
            raise
        finally:
            stop()

    address = 'inproc://vip'
    try:

        # Start the config store before auth so we may one day have auth use it.
        config_store = ConfigStoreService(address=address,
                                          identity=CONFIGURATION_STORE)

        event = gevent.event.Event()
        config_store_task = gevent.spawn(config_store.core.run, event)
        event.wait()
        del event

        # Ensure auth service is running before router
        auth_file = os.path.join(opts.volttron_home, 'auth.json')
        auth = AuthService(auth_file,
                           protected_topics_file,
                           opts.setup_mode,
                           opts.aip,
                           address=address,
                           identity=AUTH,
                           enable_store=False)

        event = gevent.event.Event()
        auth_task = gevent.spawn(auth.core.run, event)
        event.wait()
        del event
        protected_topics = auth.get_protected_topics()
        _log.debug(
            "MAIN: protected topics content {}".format(protected_topics))

        # Start router in separate thread to remain responsive
        thread = threading.Thread(target=router, args=(auth.core.stop, ))
        thread.daemon = True
        thread.start()

        gevent.sleep(0.1)
        if not thread.isAlive():
            sys.exit()

        # The instance file is where we are going to record the instance and
        # its details according to
        instance_file = os.path.expanduser('~/.volttron_instances')
        try:
            instances = load_create_store(instance_file)
        except ValueError:
            os.remove(instance_file)
            instances = load_create_store(instance_file)
        this_instance = instances.get(opts.volttron_home, {})
        this_instance['pid'] = os.getpid()
        this_instance['version'] = __version__
        # note vip_address is a list
        this_instance['vip-address'] = opts.vip_address
        this_instance['volttron-home'] = opts.volttron_home
        this_instance['volttron-root'] = os.path.abspath('../..')
        this_instance['start-args'] = sys.argv[1:]
        instances[opts.volttron_home] = this_instance
        instances.async_sync()

        protected_topics_file = os.path.join(opts.volttron_home,
                                             'protected_topics.json')
        _log.debug('protected topics file %s', protected_topics_file)
        external_address_file = os.path.join(opts.volttron_home,
                                             'external_address.json')
        _log.debug('external_address_file file %s', external_address_file)

        # Launch additional services and wait for them to start before
        # auto-starting agents
        services = [
            ControlService(opts.aip,
                           address=address,
                           identity='control',
                           tracker=tracker,
                           heartbeat_autostart=True,
                           enable_store=False,
                           enable_channel=True),
            CompatPubSub(address=address,
                         identity='pubsub.compat',
                         publish_address=opts.publish_address,
                         subscribe_address=opts.subscribe_address),
            MasterWebService(
                serverkey=publickey,
                identity=MASTER_WEB,
                address=address,
                bind_web_address=opts.bind_web_address,
                volttron_central_address=opts.volttron_central_address,
                aip=opts.aip,
                enable_store=False),
            KeyDiscoveryAgent(address=address,
                              serverkey=publickey,
                              identity='keydiscovery',
                              external_address_config=external_address_file,
                              setup_mode=opts.setup_mode,
                              bind_web_address=opts.bind_web_address),
            PubSubWrapper(address=address,
                          identity='pubsub',
                          heartbeat_autostart=True,
                          enable_store=False)
        ]
        events = [gevent.event.Event() for service in services]
        tasks = [
            gevent.spawn(service.core.run, event)
            for service, event in zip(services, events)
        ]
        tasks.append(config_store_task)
        tasks.append(auth_task)
        gevent.wait(events)
        del events

        # Auto-start agents now that all services are up
        if opts.autostart:
            for name, error in opts.aip.autostart():
                _log.error('error starting {!r}: {}\n'.format(name, error))
        # Wait for any service to stop, signaling exit
        try:
            gevent.wait(tasks, count=1)
        except KeyboardInterrupt:
            _log.info('SIGINT received; shutting down')
            sys.stderr.write('Shutting down.\n')
            for task in tasks:
                task.kill(block=False)
            gevent.wait(tasks)
    finally:
        opts.aip.finish()
def main(argv=sys.argv):
    # Refuse to run as root
    if not getattr(os, 'getuid', lambda: -1)():
        sys.stderr.write('%s: error: refusing to run as root to prevent '
                         'potential damage.\n' % os.path.basename(argv[0]))
        sys.exit(77)

    volttron_home = os.path.normpath(
        config.expandall(os.environ.get('VOLTTRON_HOME', '~/.volttron')))
    os.environ['VOLTTRON_HOME'] = volttron_home

    # Setup option parser
    parser = config.ArgumentParser(
        prog=os.path.basename(argv[0]),
        add_help=False,
        description='VOLTTRON platform service',
        usage='%(prog)s [OPTION]...',
        argument_default=argparse.SUPPRESS,
        epilog='Boolean options, which take no argument, may be inversed by '
        'prefixing the option with no- (e.g. --autostart may be '
        'inversed using --no-autostart).')
    parser.add_argument('-c',
                        '--config',
                        metavar='FILE',
                        action='parse_config',
                        ignore_unknown=False,
                        sections=[None, 'volttron'],
                        help='read configuration from FILE')
    parser.add_argument('-l',
                        '--log',
                        metavar='FILE',
                        default=None,
                        help='send log output to FILE instead of stderr')
    parser.add_argument('-L',
                        '--log-config',
                        metavar='FILE',
                        help='read logging configuration from FILE')
    parser.add_argument('--log-level',
                        metavar='LOGGER:LEVEL',
                        action=LogLevelAction,
                        help='override default logger logging level')
    parser.add_argument('--monitor',
                        action='store_true',
                        help='monitor and log connections (implies -v)')
    parser.add_argument(
        '-q',
        '--quiet',
        action='add_const',
        const=10,
        dest='verboseness',
        help='decrease logger verboseness; may be used multiple times')
    parser.add_argument(
        '-v',
        '--verbose',
        action='add_const',
        const=-10,
        dest='verboseness',
        help='increase logger verboseness; may be used multiple times')
    parser.add_argument('--verboseness',
                        type=int,
                        metavar='LEVEL',
                        default=logging.WARNING,
                        help='set logger verboseness')
    # parser.add_argument(
    #    '--volttron-home', env_var='VOLTTRON_HOME', metavar='PATH',
    #    help='VOLTTRON configuration directory')
    parser.add_argument('--show-config',
                        action='store_true',
                        help=argparse.SUPPRESS)
    parser.add_help_argument()
    parser.add_version_argument(version='%(prog)s ' + __version__)

    agents = parser.add_argument_group('agent options')
    agents.add_argument('--autostart',
                        action='store_true',
                        inverse='--no-autostart',
                        help='automatically start enabled agents and services')
    agents.add_argument('--no-autostart',
                        action='store_false',
                        dest='autostart',
                        help=argparse.SUPPRESS)
    agents.add_argument(
        '--publish-address',
        metavar='ZMQADDR',
        help='ZeroMQ URL used for pre-3.x agent publishing (deprecated)')
    agents.add_argument(
        '--subscribe-address',
        metavar='ZMQADDR',
        help='ZeroMQ URL used for pre-3.x agent subscriptions (deprecated)')
    agents.add_argument('--vip-address',
                        metavar='ZMQADDR',
                        action='append',
                        default=[],
                        help='ZeroMQ URL to bind for VIP connections')
    agents.add_argument(
        '--vip-local-address',
        metavar='ZMQADDR',
        help='ZeroMQ URL to bind for local agent VIP connections')
    agents.add_argument(
        '--bind-web-address',
        metavar='BINDWEBADDR',
        default=None,
        help='Bind a web server to the specified ip:port passed')
    agents.add_argument(
        '--volttron-central-address',
        default=None,
        help='The web address of a volttron central install instance.')
    agents.add_argument('--volttron-central-serverkey',
                        default=None,
                        help='The serverkey of volttron central.')
    agents.add_argument(
        '--instance-name',
        default=None,
        help='The name of the instance that will be reported to '
        'VOLTTRON central.')
    agents.add_argument('--msgdebug',
                        action='store_true',
                        help='Route all messages to an agent while debugging.')
    agents.add_argument(
        '--setup-mode',
        action='store_true',
        help=
        'Setup mode flag for setting up authorization of external platforms.')

    # XXX: re-implement control options
    # on
    # control.add_argument(
    #    '--allow-root', action='store_true', inverse='--no-allow-root',
    #    help='allow root to connect to control socket')
    # control.add_argument(
    #    '--no-allow-root', action='store_false', dest='allow_root',
    #    help=argparse.SUPPRESS)
    # control.add_argument(
    #    '--allow-users', action='store_list', metavar='LIST',
    #    help='users allowed to connect to control socket')
    # control.add_argument(
    #    '--allow-groups', action='store_list', metavar='LIST',
    #    help='user groups allowed to connect to control socket')

    if HAVE_RESTRICTED:

        class RestrictedAction(argparse.Action):
            def __init__(self,
                         option_strings,
                         dest,
                         const=True,
                         help=None,
                         **kwargs):
                super(RestrictedAction, self).__init__(option_strings,
                                                       dest=argparse.SUPPRESS,
                                                       nargs=0,
                                                       const=const,
                                                       help=help)

            def __call__(self, parser, namespace, values, option_string=None):
                namespace.verify_agents = self.const
                namespace.resource_monitor = self.const
                # namespace.mobility = self.const

        restrict = parser.add_argument_group('restricted options')
        restrict.add_argument(
            '--restricted',
            action=RestrictedAction,
            inverse='--no-restricted',
            help='shortcut to enable all restricted features')
        restrict.add_argument('--no-restricted',
                              action=RestrictedAction,
                              const=False,
                              help=argparse.SUPPRESS)
        restrict.add_argument('--verify',
                              action='store_true',
                              inverse='--no-verify',
                              help='verify agent integrity before execution')
        restrict.add_argument('--no-verify',
                              action='store_false',
                              dest='verify_agents',
                              help=argparse.SUPPRESS)
        restrict.add_argument('--resource-monitor',
                              action='store_true',
                              inverse='--no-resource-monitor',
                              help='enable agent resource management')
        restrict.add_argument('--no-resource-monitor',
                              action='store_false',
                              dest='resource_monitor',
                              help=argparse.SUPPRESS)
        # restrict.add_argument(
        #    '--mobility', action='store_true', inverse='--no-mobility',
        #    help='enable agent mobility')
        # restrict.add_argument(
        #    '--no-mobility', action='store_false', dest='mobility',
        #    help=argparse.SUPPRESS)

    ipc = 'ipc://%s$VOLTTRON_HOME/run/' % (
        '@' if sys.platform.startswith('linux') else '')
    parser.set_defaults(
        log=None,
        log_config=None,
        monitor=False,
        verboseness=logging.WARNING,
        volttron_home=volttron_home,
        autostart=True,
        publish_address=ipc + 'publish',
        subscribe_address=ipc + 'subscribe',
        vip_address=[],
        vip_local_address=ipc + 'vip.socket',
        # This is used to start the web server from the web module.
        bind_web_address=None,
        # Used to contact volttron central when registering volttron central
        # platform agent.
        volttron_central_address=None,
        volttron_central_serverkey=None,
        instance_name=None,
        # allow_root=False,
        # allow_users=None,
        # allow_groups=None,
        verify_agents=True,
        resource_monitor=True,
        # mobility=True,
        msgdebug=None,
        setup_mode=False)

    # Parse and expand options
    args = argv[1:]
    conf = os.path.join(volttron_home, 'config')
    if os.path.exists(conf) and 'SKIP_VOLTTRON_CONFIG' not in os.environ:
        args = ['--config', conf] + args
    logging.getLogger().setLevel(logging.NOTSET)
    opts = parser.parse_args(args)
    start_volttron_process(opts)
Exemple #3
0
def main(argv=sys.argv):
    # Refuse to run as root
    if not getattr(os, 'getuid', lambda: -1)():
        sys.stderr.write('%s: error: refusing to run as root to prevent '
                         'potential damage.\n' % os.path.basename(argv[0]))
        sys.exit(77)

    volttron_home = os.path.normpath(
        config.expandall(os.environ.get('VOLTTRON_HOME', '~/.volttron')))
    os.environ['VOLTTRON_HOME'] = volttron_home

    # Setup option parser
    parser = config.ArgumentParser(
        prog=os.path.basename(argv[0]),
        add_help=False,
        description='VOLTTRON platform service',
        usage='%(prog)s [OPTION]...',
        argument_default=argparse.SUPPRESS,
        epilog='Boolean options, which take no argument, may be inversed by '
        'prefixing the option with no- (e.g. --autostart may be '
        'inversed using --no-autostart).')
    parser.add_argument('-c',
                        '--config',
                        metavar='FILE',
                        action='parse_config',
                        ignore_unknown=True,
                        sections=[None, 'volttron'],
                        help='read configuration from FILE')
    parser.add_argument('-l',
                        '--log',
                        metavar='FILE',
                        default=None,
                        help='send log output to FILE instead of stderr')
    parser.add_argument('-L',
                        '--log-config',
                        metavar='FILE',
                        help='read logging configuration from FILE')
    parser.add_argument('--log-level',
                        metavar='LOGGER:LEVEL',
                        action=LogLevelAction,
                        help='override default logger logging level')
    parser.add_argument('--monitor',
                        action='store_true',
                        help='monitor and log connections (implies -v)')
    parser.add_argument(
        '-q',
        '--quiet',
        action='add_const',
        const=10,
        dest='verboseness',
        help='decrease logger verboseness; may be used multiple times')
    parser.add_argument(
        '-v',
        '--verbose',
        action='add_const',
        const=-10,
        dest='verboseness',
        help='increase logger verboseness; may be used multiple times')
    parser.add_argument('--verboseness',
                        type=int,
                        metavar='LEVEL',
                        default=logging.WARNING,
                        help='set logger verboseness')
    #parser.add_argument(
    #    '--volttron-home', env_var='VOLTTRON_HOME', metavar='PATH',
    #    help='VOLTTRON configuration directory')
    parser.add_argument('--show-config',
                        action='store_true',
                        help=argparse.SUPPRESS)
    parser.add_help_argument()
    parser.add_version_argument(version='%(prog)s ' + __version__)

    agents = parser.add_argument_group('agent options')
    agents.add_argument('--autostart',
                        action='store_true',
                        inverse='--no-autostart',
                        help='automatically start enabled agents and services')
    agents.add_argument('--no-autostart',
                        action='store_false',
                        dest='autostart',
                        help=argparse.SUPPRESS)
    agents.add_argument(
        '--publish-address',
        metavar='ZMQADDR',
        help='ZeroMQ URL used for pre-3.x agent publishing (deprecated)')
    agents.add_argument(
        '--subscribe-address',
        metavar='ZMQADDR',
        help='ZeroMQ URL used for pre-3.x agent subscriptions (deprecated)')
    agents.add_argument('--vip-address',
                        metavar='ZMQADDR',
                        action='append',
                        default=[],
                        help='ZeroMQ URL to bind for VIP connections')
    agents.add_argument(
        '--vip-local-address',
        metavar='ZMQADDR',
        help='ZeroMQ URL to bind for local agent VIP connections')

    # XXX: re-implement control options
    #on
    #control.add_argument(
    #    '--allow-root', action='store_true', inverse='--no-allow-root',
    #    help='allow root to connect to control socket')
    #control.add_argument(
    #    '--no-allow-root', action='store_false', dest='allow_root',
    #    help=argparse.SUPPRESS)
    #control.add_argument(
    #    '--allow-users', action='store_list', metavar='LIST',
    #    help='users allowed to connect to control socket')
    #control.add_argument(
    #    '--allow-groups', action='store_list', metavar='LIST',
    #    help='user groups allowed to connect to control socket')

    if HAVE_RESTRICTED:

        class RestrictedAction(argparse.Action):
            def __init__(self,
                         option_strings,
                         dest,
                         const=True,
                         help=None,
                         **kwargs):
                super(RestrictedAction, self).__init__(option_strings,
                                                       dest=argparse.SUPPRESS,
                                                       nargs=0,
                                                       const=const,
                                                       help=help)

            def __call__(self, parser, namespace, values, option_string=None):
                namespace.verify_agents = self.const
                namespace.resource_monitor = self.const
                #namespace.mobility = self.const

        restrict = parser.add_argument_group('restricted options')
        restrict.add_argument(
            '--restricted',
            action=RestrictedAction,
            inverse='--no-restricted',
            help='shortcut to enable all restricted features')
        restrict.add_argument('--no-restricted',
                              action=RestrictedAction,
                              const=False,
                              help=argparse.SUPPRESS)
        restrict.add_argument('--verify',
                              action='store_true',
                              inverse='--no-verify',
                              help='verify agent integrity before execution')
        restrict.add_argument('--no-verify',
                              action='store_false',
                              dest='verify_agents',
                              help=argparse.SUPPRESS)
        restrict.add_argument('--resource-monitor',
                              action='store_true',
                              inverse='--no-resource-monitor',
                              help='enable agent resource management')
        restrict.add_argument('--no-resource-monitor',
                              action='store_false',
                              dest='resource_monitor',
                              help=argparse.SUPPRESS)
        #restrict.add_argument(
        #    '--mobility', action='store_true', inverse='--no-mobility',
        #    help='enable agent mobility')
        #restrict.add_argument(
        #    '--no-mobility', action='store_false', dest='mobility',
        #    help=argparse.SUPPRESS)

    ipc = 'ipc://%s$VOLTTRON_HOME/run/' % (
        '@' if sys.platform.startswith('linux') else '')
    parser.set_defaults(
        log=None,
        log_config=None,
        monitor=False,
        verboseness=logging.WARNING,
        volttron_home=volttron_home,
        autostart=True,
        publish_address=ipc + 'publish',
        subscribe_address=ipc + 'subscribe',
        vip_address=[],
        vip_local_address=ipc + 'vip.socket',
        #allow_root=False,
        #allow_users=None,
        #allow_groups=None,
        verify_agents=True,
        resource_monitor=True,
        #mobility=True,
    )

    # Parse and expand options
    args = argv[1:]
    conf = os.path.join(volttron_home, 'config')
    if os.path.exists(conf) and 'SKIP_VOLTTRON_CONFIG' not in os.environ:
        args = ['--config', conf] + args
    logging.getLogger().setLevel(logging.NOTSET)
    opts = parser.parse_args(args)

    if opts.log:
        opts.log = config.expandall(opts.log)
    if opts.log_config:
        opts.log_config = config.expandall(opts.log_config)
    opts.publish_address = config.expandall(opts.publish_address)
    opts.subscribe_address = config.expandall(opts.subscribe_address)
    opts.vip_address = [config.expandall(addr) for addr in opts.vip_address]
    opts.vip_local_address = config.expandall(opts.vip_local_address)
    if getattr(opts, 'show_config', False):
        for name, value in sorted(vars(opts).iteritems()):
            print(name, repr(value))
        return
    # Configure logging
    level = max(1, opts.verboseness)
    if opts.monitor and level > logging.INFO:
        level = logging.INFO
    if opts.log is None:
        log_to_file(sys.stderr, level)
    elif opts.log == '-':
        log_to_file(sys.stdout, level)
    elif opts.log:
        log_to_file(opts.log, level, handler_class=handlers.WatchedFileHandler)
    else:
        log_to_file(None, 100, handler_class=lambda x: logging.NullHandler())
    if opts.log_config:
        error = configure_logging(opts.log_config)
        if error:
            parser.error('{}: {}'.format(*error))

    # Increase open files resource limit to max or 8192 if unlimited
    try:
        soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
    except OSError:
        _log.exception('error getting open file limits')
    else:
        if soft != hard and soft != resource.RLIM_INFINITY:
            try:
                limit = 8192 if hard == resource.RLIM_INFINITY else hard
                resource.setrlimit(resource.RLIMIT_NOFILE, (limit, hard))
            except OSError:
                _log.exception('error setting open file limits')
            else:
                _log.debug('open file resource limit increased from %d to %d',
                           soft, limit)

    # Set configuration
    if HAVE_RESTRICTED:
        if opts.verify_agents:
            _log.info('Agent integrity verification enabled')
        if opts.resource_monitor:
            _log.info('Resource monitor enabled')
            opts.resmon = resmon.ResourceMonitor()
    opts.aip = aip.AIPplatform(opts)
    opts.aip.setup()

    # Check for secure mode/permissions on VOLTTRON_HOME directory
    mode = os.stat(volttron_home).st_mode
    if mode & (stat.S_IWGRP | stat.S_IWOTH):
        _log.warning('insecure mode on directory: %s', volttron_home)
    # Get or generate encryption key
    keyfile = os.path.join(volttron_home, 'curve.key')
    _log.debug('using key file %s', keyfile)
    try:
        st = os.stat(keyfile)
    except OSError as exc:
        if exc.errno != errno.ENOENT:
            parser.error(str(exc))
        # Key doesn't exist, so create it securely
        _log.info('generating missing key file')
        try:
            fd = os.open(keyfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
        except OSError as exc:
            parser.error(str(exc))
        try:
            key = ''.join(curve_keypair())
            os.write(fd, key)
        finally:
            os.close(fd)
    else:
        if st.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
            _log.warning('insecure mode on key file')
        if not st.st_size:
            _log.warning('empty key file; VIP encryption is disabled!')
            key = ''
        else:
            # Allow two extra bytes in case someone opened the file with
            # a text editor and it appended '\n' or '\r\n'.
            if not 80 <= st.st_size <= 82:
                _log.warning('key file is wrong size; connections may fail')
            with open(keyfile) as infile:
                key = infile.read(80)
    publickey = key[:40]
    if publickey:
        _log.info('public key: %r (%s)', publickey, encode_key(publickey))
    secretkey = key[40:]

    # The following line doesn't appear to do anything, but it creates
    # a context common to the green and non-green zmq modules.
    zmq.Context.instance()  # DO NOT REMOVE LINE!!

    # Main loops
    def router(stop):
        try:
            Router(opts.vip_local_address,
                   opts.vip_address,
                   secretkey=secretkey,
                   default_user_id=b'vip.service',
                   monitor=opts.monitor).run()
        except Exception:
            _log.exception('Unhandled exception in router loop')
        finally:
            stop()

    address = 'inproc://vip'
    try:
        # Ensure auth service is running before router
        auth = AuthService(address=address, identity='auth')
        event = gevent.event.Event()
        auth_task = gevent.spawn(auth.core.run, event)
        event.wait()
        del event

        # Start router in separate thread to remain responsive
        thread = threading.Thread(target=router, args=(auth.core.stop, ))
        thread.daemon = True
        thread.start()

        # Launch additional services and wait for them to start before
        # auto-starting agents
        services = [
            ControlService(opts.aip, address=address, identity='control'),
            PubSubService(address=address, identity='pubsub'),
            CompatPubSub(address=address,
                         identity='pubsub.compat',
                         publish_address=opts.publish_address,
                         subscribe_address=opts.subscribe_address),
        ]
        events = [gevent.event.Event() for service in services]
        tasks = [
            gevent.spawn(service.core.run, event)
            for service, event in zip(services, events)
        ]
        tasks.append(auth_task)
        gevent.wait(events)
        del events

        # Auto-start agents now that all services are up
        if opts.autostart:
            for name, error in opts.aip.autostart():
                _log.error('error starting {!r}: {}\n'.format(name, error))
        # Wait for any service to stop, signaling exit
        try:
            gevent.wait(tasks, count=1)
        except KeyboardInterrupt:
            _log.info('SIGINT received; shutting down')
            sys.stderr.write('Shutting down.\n')
            for task in tasks:
                task.kill(block=False)
            gevent.wait(tasks)
    finally:
        opts.aip.finish()
Exemple #4
0
def main(argv=sys.argv):
    # Refuse to run as root
    if not getattr(os, 'getuid', lambda: -1)():
        sys.stderr.write('%s: error: refusing to run as root to prevent '
                         'potential damage.\n' % os.path.basename(argv[0]))
        sys.exit(77)

    volttron_home = os.path.normpath(config.expandall(
        os.environ.get('VOLTTRON_HOME', '~/.volttron')))
    os.environ['VOLTTRON_HOME'] = volttron_home

    # Setup option parser
    parser = config.ArgumentParser(
        prog=os.path.basename(argv[0]), add_help=False,
        description='VOLTTRON platform service',
        usage='%(prog)s [OPTION]...',
        argument_default=argparse.SUPPRESS,
        epilog='Boolean options, which take no argument, may be inversed by '
               'prefixing the option with no- (e.g. --autostart may be '
               'inversed using --no-autostart).'
    )
    parser.add_argument(
        '-c', '--config', metavar='FILE', action='parse_config',
        ignore_unknown=True, sections=[None, 'volttron'],
        help='read configuration from FILE')
    parser.add_argument(
        '-l', '--log', metavar='FILE', default=None,
        help='send log output to FILE instead of stderr')
    parser.add_argument(
        '-L', '--log-config', metavar='FILE',
        help='read logging configuration from FILE')
    parser.add_argument(
        '--log-level', metavar='LOGGER:LEVEL', action=LogLevelAction,
        help='override default logger logging level')
    parser.add_argument(
        '--monitor', action='store_true',
        help='monitor and log connections (implies -v)')
    parser.add_argument(
        '-q', '--quiet', action='add_const', const=10, dest='verboseness',
        help='decrease logger verboseness; may be used multiple times')
    parser.add_argument(
        '-v', '--verbose', action='add_const', const=-10, dest='verboseness',
        help='increase logger verboseness; may be used multiple times')
    parser.add_argument(
        '--verboseness', type=int, metavar='LEVEL', default=logging.WARNING,
        help='set logger verboseness')
    #parser.add_argument(
    #    '--volttron-home', env_var='VOLTTRON_HOME', metavar='PATH',
    #    help='VOLTTRON configuration directory')
    parser.add_argument(
        '--show-config', action='store_true',
        help=argparse.SUPPRESS)
    parser.add_help_argument()
    parser.add_version_argument(version='%(prog)s ' + __version__)

    agents = parser.add_argument_group('agent options')
    agents.add_argument(
        '--autostart', action='store_true', inverse='--no-autostart',
        help='automatically start enabled agents and services')
    agents.add_argument(
        '--no-autostart', action='store_false', dest='autostart',
        help=argparse.SUPPRESS)
    agents.add_argument(
        '--publish-address', metavar='ZMQADDR',
        help='ZeroMQ URL used for pre-3.x agent publishing (deprecated)')
    agents.add_argument(
        '--subscribe-address', metavar='ZMQADDR',
        help='ZeroMQ URL used for pre-3.x agent subscriptions (deprecated)')
    agents.add_argument(
        '--vip-address', metavar='ZMQADDR', action='append', default=[],
        help='ZeroMQ URL to bind for VIP connections')
    agents.add_argument(
        '--vip-local-address', metavar='ZMQADDR',
        help='ZeroMQ URL to bind for local agent VIP connections')

    # XXX: re-implement control options
    #on
    #control.add_argument(
    #    '--allow-root', action='store_true', inverse='--no-allow-root',
    #    help='allow root to connect to control socket')
    #control.add_argument(
    #    '--no-allow-root', action='store_false', dest='allow_root',
    #    help=argparse.SUPPRESS)
    #control.add_argument(
    #    '--allow-users', action='store_list', metavar='LIST',
    #    help='users allowed to connect to control socket')
    #control.add_argument(
    #    '--allow-groups', action='store_list', metavar='LIST',
    #    help='user groups allowed to connect to control socket')

    if HAVE_RESTRICTED:
        class RestrictedAction(argparse.Action):
            def __init__(self, option_strings, dest,
                         const=True, help=None, **kwargs):
                super(RestrictedAction, self).__init__(
                    option_strings, dest=argparse.SUPPRESS, nargs=0,
                    const=const, help=help)
            def __call__(self, parser, namespace, values, option_string=None):
                namespace.verify_agents = self.const
                namespace.resource_monitor = self.const
                #namespace.mobility = self.const
        restrict = parser.add_argument_group('restricted options')
        restrict.add_argument(
            '--restricted', action=RestrictedAction, inverse='--no-restricted',
            help='shortcut to enable all restricted features')
        restrict.add_argument(
            '--no-restricted', action=RestrictedAction, const=False,
            help=argparse.SUPPRESS)
        restrict.add_argument(
            '--verify', action='store_true', inverse='--no-verify',
            help='verify agent integrity before execution')
        restrict.add_argument(
            '--no-verify', action='store_false', dest='verify_agents',
            help=argparse.SUPPRESS)
        restrict.add_argument(
            '--resource-monitor', action='store_true',
            inverse='--no-resource-monitor',
            help='enable agent resource management')
        restrict.add_argument(
            '--no-resource-monitor', action='store_false',
            dest='resource_monitor', help=argparse.SUPPRESS)
        #restrict.add_argument(
        #    '--mobility', action='store_true', inverse='--no-mobility',
        #    help='enable agent mobility')
        #restrict.add_argument(
        #    '--no-mobility', action='store_false', dest='mobility',
        #    help=argparse.SUPPRESS)

    ipc = 'ipc://%s$VOLTTRON_HOME/run/' % (
        '@' if sys.platform.startswith('linux') else '')
    parser.set_defaults(
        log=None,
        log_config=None,
        monitor=False,
        verboseness=logging.WARNING,
        volttron_home=volttron_home,
        autostart=True,
        publish_address=ipc + 'publish',
        subscribe_address=ipc + 'subscribe',
        vip_address=[],
        vip_local_address=ipc + 'vip.socket',
        #allow_root=False,
        #allow_users=None,
        #allow_groups=None,
        verify_agents=True,
        resource_monitor=True,
        #mobility=True,
    )

    # Parse and expand options
    args = argv[1:]
    conf = os.path.join(volttron_home, 'config')
    if os.path.exists(conf) and 'SKIP_VOLTTRON_CONFIG' not in os.environ:
        args = ['--config', conf] + args
    logging.getLogger().setLevel(logging.NOTSET)
    opts = parser.parse_args(args)

    if opts.log:
        opts.log = config.expandall(opts.log)
    if opts.log_config:
        opts.log_config = config.expandall(opts.log_config)
    opts.publish_address = config.expandall(opts.publish_address)
    opts.subscribe_address = config.expandall(opts.subscribe_address)
    opts.vip_address = [config.expandall(addr) for addr in opts.vip_address]
    opts.vip_local_address = config.expandall(opts.vip_local_address)
    if getattr(opts, 'show_config', False):
        for name, value in sorted(vars(opts).iteritems()):
            print(name, repr(value))
        return
    # Configure logging
    level = max(1, opts.verboseness)
    if opts.monitor and level > logging.INFO:
        level = logging.INFO
    if opts.log is None:
        log_to_file(sys.stderr, level)
    elif opts.log == '-':
        log_to_file(sys.stdout, level)
    elif opts.log:
        log_to_file(opts.log, level, handler_class=handlers.WatchedFileHandler)
    else:
        log_to_file(None, 100, handler_class=lambda x: logging.NullHandler())
    if opts.log_config:
        error = configure_logging(opts.log_config)
        if error:
            parser.error('{}: {}'.format(*error))

    # Increase open files resource limit to max or 8192 if unlimited
    try:
        soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
    except OSError:
        _log.exception('error getting open file limits')
    else:
        if soft != hard and soft != resource.RLIM_INFINITY:
            try:
                limit = 8192 if hard == resource.RLIM_INFINITY else hard
                resource.setrlimit(resource.RLIMIT_NOFILE, (limit, hard))
            except OSError:
                _log.exception('error setting open file limits')
            else:
                _log.debug('open file resource limit increased from %d to %d',
                           soft, limit)

    # Set configuration
    if HAVE_RESTRICTED:
        if opts.verify_agents:
            _log.info('Agent integrity verification enabled')
        if opts.resource_monitor:
            _log.info('Resource monitor enabled')
            opts.resmon = resmon.ResourceMonitor()
    opts.aip = aip.AIPplatform(opts)
    opts.aip.setup()

    # Check for secure mode/permissions on VOLTTRON_HOME directory
    mode = os.stat(volttron_home).st_mode
    if mode & (stat.S_IWGRP | stat.S_IWOTH):
        _log.warning('insecure mode on directory: %s', volttron_home)
    # Get or generate encryption key
    keyfile = os.path.join(volttron_home, 'curve.key')
    _log.debug('using key file %s', keyfile)
    try:
        st = os.stat(keyfile)
    except OSError as exc:
        if exc.errno != errno.ENOENT:
            parser.error(str(exc))
        # Key doesn't exist, so create it securely
        _log.info('generating missing key file')
        try:
            fd = os.open(keyfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
        except OSError as exc:
            parser.error(str(exc))
        try:
            key = ''.join(curve_keypair())
            os.write(fd, key)
        finally:
            os.close(fd)
    else:
        if st.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
            _log.warning('insecure mode on key file')
        if not st.st_size:
            _log.warning('empty key file; VIP encryption is disabled!')
            key = ''
        else:
            # Allow two extra bytes in case someone opened the file with
            # a text editor and it appended '\n' or '\r\n'.
            if not 80 <= st.st_size <= 82:
                _log.warning('key file is wrong size; connections may fail')
            with open(keyfile) as infile:
                key = infile.read(80)
    publickey = key[:40]
    if publickey:
        _log.info('public key: %r (%s)', publickey, encode_key(publickey))
    secretkey = key[40:]

    # The following line doesn't appear to do anything, but it creates
    # a context common to the green and non-green zmq modules.
    zmq.Context.instance()   # DO NOT REMOVE LINE!!

    # Main loops
    def router(stop):
        try:
            Router(opts.vip_local_address, opts.vip_address,
                   secretkey=secretkey, default_user_id=b'vip.service',
                   monitor=opts.monitor).run()
        except Exception:
            _log.exception('Unhandled exception in router loop')
        finally:
            stop()

    address = 'inproc://vip'
    try:
        # Ensure auth service is running before router
        auth = AuthService(address=address, identity='auth')
        event = gevent.event.Event()
        auth_task = gevent.spawn(auth.core.run, event)
        event.wait()
        del event

        # Start router in separate thread to remain responsive
        thread = threading.Thread(target=router, args=(auth.core.stop,))
        thread.daemon = True
        thread.start()

        # Launch additional services and wait for them to start before
        # auto-starting agents
        services = [
            ControlService(opts.aip, address=address, identity='control'),
            PubSubService(address=address, identity='pubsub'),
            CompatPubSub(address=address, identity='pubsub.compat',
                         publish_address=opts.publish_address,
                         subscribe_address=opts.subscribe_address),
        ]
        events = [gevent.event.Event() for service in services]
        tasks = [gevent.spawn(service.core.run, event)
                 for service, event in zip(services, events)]
        tasks.append(auth_task)
        gevent.wait(events)
        del events

        # Auto-start agents now that all services are up
        if opts.autostart:
            for name, error in opts.aip.autostart():
                _log.error('error starting {!r}: {}\n'.format(name, error))
        # Wait for any service to stop, signaling exit
        try:
            gevent.wait(tasks, count=1)
        except KeyboardInterrupt:
            _log.info('SIGINT received; shutting down')
            sys.stderr.write('Shutting down.\n')
            for task in tasks:
                task.kill(block=False)
            gevent.wait(tasks)
    finally:
        opts.aip.finish()
Exemple #5
0
def main(argv=sys.argv):
    volttron_home = config.expandall(
        os.environ.get('VOLTTRON_HOME', '~/.volttron'))
    os.environ['VOLTTRON_HOME'] = volttron_home

    # Setup option parser
    parser = config.ArgumentParser(
        prog=os.path.basename(argv[0]),
        add_help=False,
        description='VOLTTRON platform service',
        usage='%(prog)s [OPTION]...',
        argument_default=argparse.SUPPRESS,
        epilog='Boolean options, which take no argument, may be inversed by '
        'prefixing the option with no- (e.g. --autostart may be '
        'inversed using --no-autostart).')
    parser.add_argument('-c',
                        '--config',
                        metavar='FILE',
                        action='parse_config',
                        ignore_unknown=True,
                        sections=[None, 'volttron'],
                        help='read configuration from FILE')
    parser.add_argument('-l',
                        '--log',
                        metavar='FILE',
                        default=None,
                        help='send log output to FILE instead of stderr')
    parser.add_argument('-L',
                        '--log-config',
                        metavar='FILE',
                        help='read logging configuration from FILE')
    parser.add_argument('--log-level',
                        metavar='LOGGER:LEVEL',
                        action=LogLevelAction,
                        help='override default logger logging level')
    parser.add_argument(
        '-q',
        '--quiet',
        action='add_const',
        const=10,
        dest='verboseness',
        help='decrease logger verboseness; may be used multiple times')
    parser.add_argument(
        '-v',
        '--verbose',
        action='add_const',
        const=-10,
        dest='verboseness',
        help='increase logger verboseness; may be used multiple times')
    parser.add_argument('--verboseness',
                        type=int,
                        metavar='LEVEL',
                        default=logging.WARNING,
                        help='set logger verboseness')
    #parser.add_argument(
    #    '--volttron-home', env_var='VOLTTRON_HOME', metavar='PATH',
    #    help='VOLTTRON configuration directory')
    parser.add_argument('--show-config',
                        action='store_true',
                        help=argparse.SUPPRESS)
    parser.add_help_argument()
    parser.add_version_argument(version='%(prog)s ' + __version__)

    agents = parser.add_argument_group('agent options')
    agents.add_argument('--autostart',
                        action='store_true',
                        inverse='--no-autostart',
                        help='automatically start enabled agents and services')
    agents.add_argument('--no-autostart',
                        action='store_false',
                        dest='autostart',
                        help=argparse.SUPPRESS)
    agents.add_argument('--publish-address',
                        metavar='ZMQADDR',
                        help='ZeroMQ URL used for agent publishing')
    agents.add_argument('--subscribe-address',
                        metavar='ZMQADDR',
                        help='ZeroMQ URL used for agent subscriptions')
    agents.add_argument('--vip-address',
                        metavar='ZMQADDR',
                        action='append',
                        default=[],
                        help='ZeroMQ URL to bind for VIP connections')

    # XXX: re-implement control options
    #on
    #control.add_argument(
    #    '--allow-root', action='store_true', inverse='--no-allow-root',
    #    help='allow root to connect to control socket')
    #control.add_argument(
    #    '--no-allow-root', action='store_false', dest='allow_root',
    #    help=argparse.SUPPRESS)
    #control.add_argument(
    #    '--allow-users', action='store_list', metavar='LIST',
    #    help='users allowed to connect to control socket')
    #control.add_argument(
    #    '--allow-groups', action='store_list', metavar='LIST',
    #    help='user groups allowed to connect to control socket')

    if HAVE_RESTRICTED:

        class RestrictedAction(argparse.Action):
            def __init__(self,
                         option_strings,
                         dest,
                         const=True,
                         help=None,
                         **kwargs):
                super(RestrictedAction, self).__init__(option_strings,
                                                       dest=argparse.SUPPRESS,
                                                       nargs=0,
                                                       const=const,
                                                       help=help)

            def __call__(self, parser, namespace, values, option_string=None):
                namespace.verify_agents = self.const
                namespace.resource_monitor = self.const
                namespace.mobility = self.const

        restrict = parser.add_argument_group('restricted options')
        restrict.add_argument(
            '--restricted',
            action=RestrictedAction,
            inverse='--no-restricted',
            help='shortcut to enable all restricted features')
        restrict.add_argument('--no-restricted',
                              action=RestrictedAction,
                              const=False,
                              help=argparse.SUPPRESS)
        restrict.add_argument('--verify',
                              action='store_true',
                              inverse='--no-verify',
                              help='verify agent integrity before execution')
        restrict.add_argument('--no-verify',
                              action='store_false',
                              dest='verify_agents',
                              help=argparse.SUPPRESS)
        restrict.add_argument('--resource-monitor',
                              action='store_true',
                              inverse='--no-resource-monitor',
                              help='enable agent resource management')
        restrict.add_argument('--no-resource-monitor',
                              action='store_false',
                              dest='resource_monitor',
                              help=argparse.SUPPRESS)
        restrict.add_argument('--mobility',
                              action='store_true',
                              inverse='--no-mobility',
                              help='enable agent mobility')
        restrict.add_argument('--no-mobility',
                              action='store_false',
                              dest='mobility',
                              help=argparse.SUPPRESS)
        restrict.add_argument('--mobility-address',
                              metavar='ADDRESS',
                              help='specify the address on which to listen')
        restrict.add_argument('--mobility-port',
                              type=int,
                              metavar='NUMBER',
                              help='specify the port on which to listen')

    vip_path = '$VOLTTRON_HOME/run/vip.socket'
    if sys.platform.startswith('linux'):
        vip_path = '@' + vip_path
    parser.set_defaults(
        log=None,
        log_config=None,
        verboseness=logging.WARNING,
        volttron_home=volttron_home,
        autostart=True,
        publish_address='ipc://$VOLTTRON_HOME/run/publish',
        subscribe_address='ipc://$VOLTTRON_HOME/run/subscribe',
        vip_address=['ipc://' + vip_path],
        #allow_root=False,
        #allow_users=None,
        #allow_groups=None,
        verify_agents=True,
        resource_monitor=True,
        mobility=True,
        mobility_address=None,
        mobility_port=2522)

    # Parse and expand options
    args = argv[1:]
    conf = os.path.join(volttron_home, 'config')
    if os.path.exists(conf) and 'SKIP_VOLTTRON_CONFIG' not in os.environ:
        args = ['--config', conf] + args
    logging.getLogger().setLevel(logging.NOTSET)
    opts = parser.parse_args(args)
    if opts.log:
        opts.log = config.expandall(opts.log)
    if opts.log_config:
        opts.log_config = config.expandall(opts.log_config)
    opts.publish_address = config.expandall(opts.publish_address)
    opts.subscribe_address = config.expandall(opts.subscribe_address)
    opts.vip_address = [config.expandall(addr) for addr in opts.vip_address]
    if HAVE_RESTRICTED:
        # Set mobility defaults
        if opts.mobility_address is None:
            info = socket.getaddrinfo(None, 0, 0, socket.SOCK_STREAM, 0,
                                      socket.AI_NUMERICHOST)
            family = info[0][0] if info else ''
            opts.mobility_address = '::' if family == socket.AF_INET6 else ''
    if getattr(opts, 'show_config', False):
        for name, value in sorted(vars(opts).iteritems()):
            print(name, repr(value))
        return
    # Configure logging
    level = max(1, opts.verboseness)
    if opts.log is None:
        log_to_file(sys.stderr, level)
    elif opts.log == '-':
        log_to_file(sys.stdout, level)
    elif opts.log:
        log_to_file(opts.log, level, handler_class=handlers.WatchedFileHandler)
    else:
        log_to_file(None, 100, handler_class=lambda x: logging.NullHandler())
    if opts.log_config:
        error = configure_logging(opts.log_config)
        if error:
            parser.error('{}: {}'.format(*error))

    # Setup mobility server
    if HAVE_RESTRICTED and opts.mobility:
        ssh_dir = os.path.join(opts.volttron_home, 'ssh')
        try:
            priv_key = RSAKey(filename=os.path.join(ssh_dir, 'id_rsa'))
            authorized_keys = comms.load_authorized_keys(
                os.path.join(ssh_dir, 'authorized_keys'))
        except (OSError, IOError, PasswordRequiredException,
                SSHException) as exc:
            parser.error(exc)

    # Set configuration
    if HAVE_RESTRICTED:
        if opts.verify_agents:
            _log.info('Agent integrity verification enabled')
        if opts.resource_monitor:
            _log.info('Resource monitor enabled')
            opts.resmon = resmon.ResourceMonitor()
    opts.aip = aip.AIPplatform(opts)
    opts.aip.setup()
    if opts.autostart:
        for name, error in opts.aip.autostart():
            _log.error('error starting {!r}: {}\n'.format(name, error))

    # Main loops
    try:
        router = Router(opts.vip_address)
        exchange = gevent.spawn(agent_exchange, opts.publish_address,
                                opts.subscribe_address)
        control = gevent.spawn(
            ControlService(opts.aip,
                           vip_address='inproc://vip',
                           vip_identity='control').run)
        pubsub = gevent.spawn(
            PubSubService(vip_address='inproc://vip',
                          vip_identity='pubsub').run)
        if HAVE_RESTRICTED and opts.mobility:
            address = (opts.mobility_address, opts.mobility_port)
            mobility_in = comms_server.ThreadedServer(address, priv_key,
                                                      authorized_keys,
                                                      opts.aip)
            mobility_in.start()
            mobility_out = MobilityAgent(
                opts.aip,
                subscribe_address=opts.subscribe_address,
                publish_address=opts.publish_address)
            gevent.spawn(mobility_out.run)
        try:
            router.run()
        finally:
            control.kill()
            pubsub.kill()
            exchange.kill()
    finally:
        opts.aip.finish()
Exemple #6
0
def main(argv=sys.argv):
    # Refuse to run as root
    if not getattr(os, 'getuid', lambda: -1)():
        sys.stderr.write('%s: error: refusing to run as root to prevent '
                         'potential damage.\n' % os.path.basename(argv[0]))
        sys.exit(77)

    volttron_home = os.path.normpath(config.expandall(
        os.environ.get('VOLTTRON_HOME', '~/.volttron')))
    os.environ['VOLTTRON_HOME'] = volttron_home

    # Setup option parser
    parser = config.ArgumentParser(
        prog=os.path.basename(argv[0]), add_help=False,
        description='VOLTTRON platform service',
        usage='%(prog)s [OPTION]...',
        argument_default=argparse.SUPPRESS,
        epilog='Boolean options, which take no argument, may be inversed by '
               'prefixing the option with no- (e.g. --autostart may be '
               'inversed using --no-autostart).'
    )
    parser.add_argument(
        '-c', '--config', metavar='FILE', action='parse_config',
        ignore_unknown=True, sections=[None, 'volttron'],
        help='read configuration from FILE')
    parser.add_argument(
        '--developer-mode', action='store_true',
        help='run in insecure developer mode')
    parser.add_argument(
        '-l', '--log', metavar='FILE', default=None,
        help='send log output to FILE instead of stderr')
    parser.add_argument(
        '-L', '--log-config', metavar='FILE',
        help='read logging configuration from FILE')
    parser.add_argument(
        '--log-level', metavar='LOGGER:LEVEL', action=LogLevelAction,
        help='override default logger logging level')
    parser.add_argument(
        '--monitor', action='store_true',
        help='monitor and log connections (implies -v)')
    parser.add_argument(
        '-q', '--quiet', action='add_const', const=10, dest='verboseness',
        help='decrease logger verboseness; may be used multiple times')
    parser.add_argument(
        '-v', '--verbose', action='add_const', const=-10, dest='verboseness',
        help='increase logger verboseness; may be used multiple times')
    parser.add_argument(
        '--verboseness', type=int, metavar='LEVEL', default=logging.WARNING,
        help='set logger verboseness')
    #parser.add_argument(
    #    '--volttron-home', env_var='VOLTTRON_HOME', metavar='PATH',
    #    help='VOLTTRON configuration directory')
    parser.add_argument(
        '--show-config', action='store_true',
        help=argparse.SUPPRESS)
    parser.add_help_argument()
    parser.add_version_argument(version='%(prog)s ' + __version__)

    agents = parser.add_argument_group('agent options')
    agents.add_argument(
        '--autostart', action='store_true', inverse='--no-autostart',
        help='automatically start enabled agents and services')
    agents.add_argument(
        '--no-autostart', action='store_false', dest='autostart',
        help=argparse.SUPPRESS)
    agents.add_argument(
        '--publish-address', metavar='ZMQADDR',
        help='ZeroMQ URL used for pre-3.x agent publishing (deprecated)')
    agents.add_argument(
        '--subscribe-address', metavar='ZMQADDR',
        help='ZeroMQ URL used for pre-3.x agent subscriptions (deprecated)')
    agents.add_argument(
        '--vip-address', metavar='ZMQADDR', action='append', default=[],
        help='ZeroMQ URL to bind for VIP connections')
    agents.add_argument(
        '--vip-local-address', metavar='ZMQADDR',
        help='ZeroMQ URL to bind for local agent VIP connections')
    agents.add_argument(
        '--bind-web-address', metavar='BINDWEBADDR', default=None,
        help='Bind a web server to the specified ip:port passed')
    agents.add_argument(
        '--volttron-central-address', metavar='VOLTTRONCENTRAL',
        default=None,
        help='The web address of a volttron central install instance.')
    # XXX: re-implement control options
    #on
    #control.add_argument(
    #    '--allow-root', action='store_true', inverse='--no-allow-root',
    #    help='allow root to connect to control socket')
    #control.add_argument(
    #    '--no-allow-root', action='store_false', dest='allow_root',
    #    help=argparse.SUPPRESS)
    #control.add_argument(
    #    '--allow-users', action='store_list', metavar='LIST',
    #    help='users allowed to connect to control socket')
    #control.add_argument(
    #    '--allow-groups', action='store_list', metavar='LIST',
    #    help='user groups allowed to connect to control socket')

    if HAVE_RESTRICTED:
        class RestrictedAction(argparse.Action):
            def __init__(self, option_strings, dest,
                         const=True, help=None, **kwargs):
                super(RestrictedAction, self).__init__(
                    option_strings, dest=argparse.SUPPRESS, nargs=0,
                    const=const, help=help)
            def __call__(self, parser, namespace, values, option_string=None):
                namespace.verify_agents = self.const
                namespace.resource_monitor = self.const
                #namespace.mobility = self.const
        restrict = parser.add_argument_group('restricted options')
        restrict.add_argument(
            '--restricted', action=RestrictedAction, inverse='--no-restricted',
            help='shortcut to enable all restricted features')
        restrict.add_argument(
            '--no-restricted', action=RestrictedAction, const=False,
            help=argparse.SUPPRESS)
        restrict.add_argument(
            '--verify', action='store_true', inverse='--no-verify',
            help='verify agent integrity before execution')
        restrict.add_argument(
            '--no-verify', action='store_false', dest='verify_agents',
            help=argparse.SUPPRESS)
        restrict.add_argument(
            '--resource-monitor', action='store_true',
            inverse='--no-resource-monitor',
            help='enable agent resource management')
        restrict.add_argument(
            '--no-resource-monitor', action='store_false',
            dest='resource_monitor', help=argparse.SUPPRESS)
        #restrict.add_argument(
        #    '--mobility', action='store_true', inverse='--no-mobility',
        #    help='enable agent mobility')
        #restrict.add_argument(
        #    '--no-mobility', action='store_false', dest='mobility',
        #    help=argparse.SUPPRESS)

    ipc = 'ipc://%s$VOLTTRON_HOME/run/' % (
        '@' if sys.platform.startswith('linux') else '')
    parser.set_defaults(
        log=None,
        log_config=None,
        monitor=False,
        verboseness=logging.WARNING,
        volttron_home=volttron_home,
        autostart=True,
        publish_address=ipc + 'publish',
        subscribe_address=ipc + 'subscribe',
        vip_address=[],
        vip_local_address=ipc + 'vip.socket',
        # This is used to start the web server from the web module.
        bind_web_address=None,
        # Used to contact volttron central when registering volttron central
        # platform agent.
        volttron_central_address=None,
        #allow_root=False,
        #allow_users=None,
        #allow_groups=None,
        verify_agents=True,
        resource_monitor=True,
        #mobility=True,
        developer_mode=False,
    )

    # Parse and expand options
    args = argv[1:]
    conf = os.path.join(volttron_home, 'config')
    if os.path.exists(conf) and 'SKIP_VOLTTRON_CONFIG' not in os.environ:
        args = ['--config', conf] + args
    logging.getLogger().setLevel(logging.NOTSET)
    opts = parser.parse_args(args)
    start_volttron_process(opts)
Exemple #7
0
def start_volttron_process(opts):
    '''Start the main volttron process.

    Typically this function is used from main.py and just uses the argparser's
    Options arguments as inputs.   It also can be called with a dictionary.  In
    that case the dictionaries keys are mapped into a value that acts like the
    args options.
    '''

    if isinstance(opts, dict):
        opts = type('Options', (), opts)()
        # vip_address is meant to be a list so make it so.
        if not isinstance(opts.vip_address, list):
            opts.vip_address = [opts.vip_address]
    if opts.log:
        opts.log = config.expandall(opts.log)
    if opts.log_config:
        opts.log_config = config.expandall(opts.log_config)
    opts.publish_address = config.expandall(opts.publish_address)
    opts.subscribe_address = config.expandall(opts.subscribe_address)
    opts.vip_address = [config.expandall(addr) for addr in opts.vip_address]
    opts.vip_local_address = config.expandall(opts.vip_local_address)
    import urlparse
    if opts.bind_web_address:
        parsed = urlparse.urlparse(opts.bind_web_address)
        if not parsed.scheme:
            raise StandardError(
                'bind-web-address must begin with http or https.')
        opts.bind_web_address = config.expandall(opts.bind_web_address)
    if opts.volttron_central_address:
        parsed = urlparse.urlparse(opts.volttron_central_address)
        if not parsed.scheme:
            raise StandardError(
                'volttron-central-address must begin with http or https.')
        opts.volttron_central_address = config.expandall(
            opts.volttron_central_address)
    if getattr(opts, 'show_config', False):
        for name, value in sorted(vars(opts).iteritems()):
            print(name, repr(value))
        return
    # Configure logging
    level = max(1, opts.verboseness)
    if opts.monitor and level > logging.INFO:
        level = logging.INFO
    if opts.log is None:
        log_to_file(sys.stderr, level)
    elif opts.log == '-':
        log_to_file(sys.stdout, level)
    elif opts.log:
        log_to_file(opts.log, level, handler_class=handlers.WatchedFileHandler)
    else:
        log_to_file(None, 100, handler_class=lambda x: logging.NullHandler())
    if opts.log_config:
        error = configure_logging(opts.log_config)
        if error:
            parser.error('{}: {}'.format(*error))

    # Increase open files resource limit to max or 8192 if unlimited
    try:
        soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
    except OSError:
        _log.exception('error getting open file limits')
    else:
        if soft != hard and soft != resource.RLIM_INFINITY:
            try:
                limit = 8192 if hard == resource.RLIM_INFINITY else hard
                resource.setrlimit(resource.RLIMIT_NOFILE, (limit, hard))
            except OSError:
                _log.exception('error setting open file limits')
            else:
                _log.debug('open file resource limit increased from %d to %d',
                           soft, limit)

    # Set configuration
    if HAVE_RESTRICTED:
        if opts.verify_agents:
            _log.info('Agent integrity verification enabled')
        if opts.resource_monitor:
            _log.info('Resource monitor enabled')
            opts.resmon = resmon.ResourceMonitor()
    opts.aip = aip.AIPplatform(opts)
    opts.aip.setup()

    # Check for secure mode/permissions on VOLTTRON_HOME directory
    mode = os.stat(opts.volttron_home).st_mode
    if mode & (stat.S_IWGRP | stat.S_IWOTH):
        _log.warning('insecure mode on directory: %s', opts.volttron_home)
    # Get or generate encryption key
    if opts.developer_mode:
        secretkey = None
        publickey = None
        _log.warning('developer mode enabled; '
                     'authentication and encryption are disabled!')
    else:
        keyfile = os.path.join(opts.volttron_home, 'curve.key')
        _log.debug('using key file %s', keyfile)
        try:
            st = os.stat(keyfile)
        except OSError as exc:
            if exc.errno != errno.ENOENT:
                parser.error(str(exc))
            # Key doesn't exist, so create it securely
            _log.info('generating missing key file')
            try:
                fd = os.open(keyfile, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
            except OSError as exc:
                parser.error(str(exc))
            try:
                key = ''.join(curve_keypair())
                os.write(fd, key)
            finally:
                os.close(fd)
        else:
            if st.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
                _log.warning('insecure mode on key file')
            if not st.st_size:
                _log.warning('empty key file; VIP encryption is disabled!')
                key = ''
            else:
                # Allow two extra bytes in case someone opened the file with
                # a text editor and it appended '\n' or '\r\n'.
                if not 80 <= st.st_size <= 82:
                    _log.warning('key file is wrong size; connections may fail')
                with open(keyfile) as infile:
                    key = infile.read(80)
        publickey = key[:40]
        if publickey:
            _log.info('public key: %s', encode_key(publickey))
        secretkey = key[40:]

    # The following line doesn't appear to do anything, but it creates
    # a context common to the green and non-green zmq modules.
    zmq.Context.instance()   # DO NOT REMOVE LINE!!

    tracker = Tracker()
    # Main loops
    def router(stop):
        try:
            Router(opts.vip_local_address, opts.vip_address,
                   secretkey=secretkey, default_user_id=b'vip.service',
                   monitor=opts.monitor, tracker=tracker).run()
        except Exception:
            _log.exception('Unhandled exception in router loop')
            raise
        finally:
            stop()

    address = 'inproc://vip'
    try:
        # Ensure auth service is running before router
        auth_file = os.path.join(opts.volttron_home, 'auth.json')
        auth = AuthService(
            auth_file, opts.aip, address=address, identity='auth',
            allow_any=opts.developer_mode)

        event = gevent.event.Event()
        auth_task = gevent.spawn(auth.core.run, event)
        event.wait()
        del event

        # Start router in separate thread to remain responsive
        thread = threading.Thread(target=router, args=(auth.core.stop,))
        thread.daemon = True
        thread.start()

        gevent.sleep(0.1)
        if not thread.isAlive():
            sys.exit()

        protected_topics_file = os.path.join(opts.volttron_home, 'protected_topics.json')
        _log.debug('protected topics file %s', protected_topics_file)

        # Launch additional services and wait for them to start before
        # auto-starting agents
        services = [
            ControlService(opts.aip, address=address, identity='control', tracker=tracker, heartbeat_autostart=True),
            PubSubService(protected_topics_file, address=address, identity='pubsub', heartbeat_autostart=True),
            CompatPubSub(address=address, identity='pubsub.compat',
                         publish_address=opts.publish_address,
                         subscribe_address=opts.subscribe_address),
            MasterWebService(
                serverkey=publickey, identity=MASTER_WEB,
                address=address,
                bind_web_address=opts.bind_web_address,
                volttron_central_address=opts.volttron_central_address,
                aip=opts.aip)
        ]
        events = [gevent.event.Event() for service in services]
        tasks = [gevent.spawn(service.core.run, event)
                 for service, event in zip(services, events)]
        tasks.append(auth_task)
        gevent.wait(events)
        del events

        # Auto-start agents now that all services are up
        if opts.autostart:
            for name, error in opts.aip.autostart():
                _log.error('error starting {!r}: {}\n'.format(name, error))
        # Wait for any service to stop, signaling exit
        try:
            gevent.wait(tasks, count=1)
        except KeyboardInterrupt:
            _log.info('SIGINT received; shutting down')
            sys.stderr.write('Shutting down.\n')
            for task in tasks:
                task.kill(block=False)
            gevent.wait(tasks)
    finally:
        opts.aip.finish()
Exemple #8
0
def start_volttron_process(opts):
    '''Start the main volttron process.

    Typically this function is used from main.py and just uses the argparser's
    Options arguments as inputs.   It also can be called with a dictionary.  In
    that case the dictionaries keys are mapped into a value that acts like the
    args options.
    '''

    if isinstance(opts, dict):
        opts = type('Options', (), opts)()
        # vip_address is meant to be a list so make it so.
        if not isinstance(opts.vip_address, list):
            opts.vip_address = [opts.vip_address]
    if opts.log:
        opts.log = config.expandall(opts.log)
    if opts.log_config:
        opts.log_config = config.expandall(opts.log_config)

    # Configure logging
    level = max(1, opts.verboseness)
    if opts.monitor and level > logging.INFO:
        level = logging.INFO

    if opts.log is None:
        log_to_file(sys.stderr, level)
    elif opts.log == '-':
        log_to_file(sys.stdout, level)
    elif opts.log:
        log_to_file(opts.log, level, handler_class=handlers.WatchedFileHandler)
    else:
        log_to_file(None, 100, handler_class=lambda x: logging.NullHandler())

    if opts.log_config:
        with open(opts.log_config, 'r') as f:
            for line in f.readlines():
                _log.info(line.rstrip())

        error = configure_logging(opts.log_config)

        if error:
            _log.error('{}: {}'.format(*error))
            sys.exit(1)

    opts.publish_address = config.expandall(opts.publish_address)
    opts.subscribe_address = config.expandall(opts.subscribe_address)
    opts.vip_address = [config.expandall(addr) for addr in opts.vip_address]
    opts.vip_local_address = config.expandall(opts.vip_local_address)

    if opts.instance_name is None:
        if len(opts.vip_address) > 0:
            opts.instance_name = opts.vip_address[0]
    import urlparse

    if opts.bind_web_address:
        parsed = urlparse.urlparse(opts.bind_web_address)
        if parsed.scheme not in ('http', 'https'):
            raise StandardError(
                'bind-web-address must begin with http or https.')
        opts.bind_web_address = config.expandall(opts.bind_web_address)
    if opts.volttron_central_address:
        parsed = urlparse.urlparse(opts.volttron_central_address)
        if parsed.scheme not in ('http', 'https', 'tcp'):
            raise StandardError(
                'volttron-central-address must begin with tcp, http or https.')
        opts.volttron_central_address = config.expandall(
            opts.volttron_central_address)
    opts.volttron_central_serverkey = opts.volttron_central_serverkey

    # Log configuration options
    if getattr(opts, 'show_config', False):
        _log.info('volttron version: {}'.format(__version__))
        for name, value in sorted(vars(opts).iteritems()):
            _log.info("%s: %s" % (name, str(repr(value))))

    # Increase open files resource limit to max or 8192 if unlimited
    try:
        soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)

    except OSError:
        _log.exception('error getting open file limits')
    else:
        if soft != hard and soft != resource.RLIM_INFINITY:
            try:
                limit = 8192 if hard == resource.RLIM_INFINITY else hard
                resource.setrlimit(resource.RLIMIT_NOFILE, (limit, hard))
            except OSError:
                _log.exception('error setting open file limits')
            else:
                _log.debug('open file resource limit increased from %d to %d',
                           soft, limit)
        _log.debug('open file resource limit %d to %d',
                   soft, hard)
    # Set configuration
    if HAVE_RESTRICTED:
        if opts.verify_agents:
            _log.info('Agent integrity verification enabled')
        if opts.resource_monitor:
            _log.info('Resource monitor enabled')
            opts.resmon = resmon.ResourceMonitor()

    opts.aip = aip.AIPplatform(opts)
    opts.aip.setup()

    # Check for secure mode/permissions on VOLTTRON_HOME directory
    mode = os.stat(opts.volttron_home).st_mode
    if mode & (stat.S_IWGRP | stat.S_IWOTH):
        _log.warning('insecure mode on directory: %s', opts.volttron_home)
    # Get or generate encryption key
    keystore = KeyStore()
    _log.debug('using key-store file %s', keystore.filename)
    if not keystore.isvalid():
        _log.warning('key store is invalid; connections may fail')
    st = os.stat(keystore.filename)
    if st.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
        _log.warning('insecure mode on key file')
    publickey = decode_key(keystore.public)
    if publickey:
        _log.info('public key: %s', encode_key(publickey))
        # Authorize the platform key:
        entry = AuthEntry(credentials=encode_key(publickey),
                          user_id='platform',
                          comments='Automatically added by platform on start')
        AuthFile().add(entry, overwrite=True)
        # Add platform key to known-hosts file:
        known_hosts = KnownHostsStore()
        known_hosts.add(opts.vip_local_address, encode_key(publickey))
        for addr in opts.vip_address:
            known_hosts.add(addr, encode_key(publickey))
    secretkey = decode_key(keystore.secret)

    # The following line doesn't appear to do anything, but it creates
    # a context common to the green and non-green zmq modules.
    zmq.Context.instance()  # DO NOT REMOVE LINE!!
    # zmq.Context.instance().set(zmq.MAX_SOCKETS, 2046)

    tracker = Tracker()
    protected_topics_file = os.path.join(opts.volttron_home, 'protected_topics.json')
    _log.debug('protected topics file %s', protected_topics_file)
    external_address_file = os.path.join(opts.volttron_home, 'external_address.json')
    _log.debug('external_address_file file %s', external_address_file)
    protected_topics = {}

    # Main loops
    def router(stop):
        try:
            Router(opts.vip_local_address, opts.vip_address,
                   secretkey=secretkey, publickey=publickey,
                   default_user_id=b'vip.service', monitor=opts.monitor,
                   tracker=tracker,
                   volttron_central_address=opts.volttron_central_address,
                   volttron_central_serverkey=opts.volttron_central_serverkey,
                   instance_name=opts.instance_name,
                   bind_web_address=opts.bind_web_address,
                   protected_topics=protected_topics,
                   external_address_file=external_address_file,
                   msgdebug=opts.msgdebug).run()
        except Exception:
            _log.exception('Unhandled exception in router loop')
            raise
        except KeyboardInterrupt:
            pass
        finally:
            stop()

    address = 'inproc://vip'
    try:
        def on_sigint_handler(signo, *_):
            '''
            Event handler to set onstop event when the platform wants to shutdown
            :param signo: signal interrupt number
            :param _:
            :return:
            '''
            if signo == signal.SIGINT:
                _log.info('SIGINT received; shutting down platform')
                auth.core.socket.send_vip(b'', b'quit')

        oninterrupt = None
        prev_int_signal = gevent.signal.getsignal(signal.SIGINT)
        # To override default handler
        if prev_int_signal in [None, signal.SIG_IGN, signal.SIG_DFL, signal.default_int_handler]:
            oninterrupt = gevent.signal.signal(signal.SIGINT, on_sigint_handler)

        # Start the config store before auth so we may one day have auth use it.
        config_store = ConfigStoreService(address=address, identity=CONFIGURATION_STORE)

        event = gevent.event.Event()
        config_store_task = gevent.spawn(config_store.core.run, event)
        event.wait()
        del event

        # Ensure auth service is running before router
        auth_file = os.path.join(opts.volttron_home, 'auth.json')
        auth = AuthService(
            auth_file, protected_topics_file, opts.setup_mode, opts.aip, address=address, identity=AUTH,
            enable_store=False)

        event = gevent.event.Event()
        auth_task = gevent.spawn(auth.core.run, event)
        event.wait()
        del event
        protected_topics = auth.get_protected_topics()
        _log.debug("MAIN: protected topics content {}".format(protected_topics))

        # Start router in separate thread to remain responsive
        thread = threading.Thread(target=router, args=(auth.core.stop,))
        thread.daemon = True
        thread.start()


        gevent.sleep(0.1)
        if not thread.isAlive():
            sys.exit()

        # The instance file is where we are going to record the instance and
        # its details according to
        instance_file = os.path.expanduser('~/.volttron_instances')
        try:
            instances = load_create_store(instance_file)
        except ValueError:
            os.remove(instance_file)
            instances = load_create_store(instance_file)
        this_instance = instances.get(opts.volttron_home, {})
        this_instance['pid'] = os.getpid()
        this_instance['version'] = __version__
        # note vip_address is a list
        this_instance['vip-address'] = opts.vip_address
        this_instance['volttron-home'] = opts.volttron_home
        this_instance['volttron-root'] = os.path.abspath('../..')
        this_instance['start-args'] = sys.argv[1:]
        instances[opts.volttron_home] = this_instance
        instances.async_sync()

        protected_topics_file = os.path.join(opts.volttron_home, 'protected_topics.json')
        _log.debug('protected topics file %s', protected_topics_file)
        external_address_file = os.path.join(opts.volttron_home, 'external_address.json')
        _log.debug('external_address_file file %s', external_address_file)

        # Launch additional services and wait for them to start before
        # auto-starting agents
        services = [
            ControlService(opts.aip, address=address, identity='control',
                           tracker=tracker, heartbeat_autostart=True,
                           enable_store=False, enable_channel=True),

            CompatPubSub(address=address, identity='pubsub.compat',
                         publish_address=opts.publish_address,
                         subscribe_address=opts.subscribe_address),

            MasterWebService(
                serverkey=publickey, identity=MASTER_WEB,
                address=address,
                bind_web_address=opts.bind_web_address,
                volttron_central_address=opts.volttron_central_address,
                aip=opts.aip, enable_store=False),

            KeyDiscoveryAgent(address=address, serverkey=publickey,
                              identity='keydiscovery',
                              external_address_config=external_address_file,
                              setup_mode=opts.setup_mode,
                              bind_web_address=opts.bind_web_address),

            PubSubWrapper(address=address,
                          identity='pubsub', heartbeat_autostart=True,
                          enable_store=False)
        ]
        events = [gevent.event.Event() for service in services]
        tasks = [gevent.spawn(service.core.run, event)
                 for service, event in zip(services, events)]
        tasks.append(config_store_task)
        tasks.append(auth_task)
        gevent.wait(events)
        del events

        # Auto-start agents now that all services are up
        if opts.autostart:
            for name, error in opts.aip.autostart():
                _log.error('error starting {!r}: {}\n'.format(name, error))
        # Wait for any service to stop, signaling exit
        try:
            gevent.wait(tasks, count=1)
        except KeyboardInterrupt:
            _log.debug('SIGINT received; shutting down')
        finally:
            sys.stderr.write('Shutting down.\n')
            for task in tasks:
                task.kill(block=False)
            gevent.wait(tasks)
            del tasks
    finally:
        opts.aip.finish()
Exemple #9
0
def main(argv=sys.argv):
    volttron_home = config.expandall(
        os.environ.get('VOLTTRON_HOME', '~/.volttron'))
    os.environ['VOLTTRON_HOME'] = volttron_home

    # Setup option parser
    parser = config.ArgumentParser(
        prog=os.path.basename(argv[0]), add_help=False,
        description='VOLTTRON platform service',
        usage='%(prog)s [OPTION]...',
        argument_default=argparse.SUPPRESS,
        epilog='Boolean options, which take no argument, may be inversed by '
               'prefixing the option with no- (e.g. --autostart may be '
               'inversed using --no-autostart).'
    )
    parser.add_argument(
        '-c', '--config', metavar='FILE', action='parse_config',
        ignore_unknown=True, sections=[None, 'volttron'],
        help='read configuration from FILE')
    parser.add_argument(
        '-l', '--log', metavar='FILE', default=None,
        help='send log output to FILE instead of stderr')
    parser.add_argument(
        '-L', '--log-config', metavar='FILE',
        help='read logging configuration from FILE')
    parser.add_argument(
        '-q', '--quiet', action='add_const', const=10, dest='verboseness',
        help='decrease logger verboseness; may be used multiple times')
    parser.add_argument(
        '-v', '--verbose', action='add_const', const=-10, dest='verboseness',
        help='increase logger verboseness; may be used multiple times')
    parser.add_argument(
        '--verboseness', type=int, metavar='LEVEL', default=logging.WARNING,
        help='set logger verboseness')
    #parser.add_argument(
    #    '--volttron-home', env_var='VOLTTRON_HOME', metavar='PATH',
    #    help='VOLTTRON configuration directory')
    parser.add_argument(
        '--show-config', action='store_true',
        help=argparse.SUPPRESS)
    parser.add_help_argument()
    parser.add_version_argument(version='%(prog)s ' + __version__)

    agents = parser.add_argument_group('agent options')
    agents.add_argument(
        '--autostart', action='store_true', inverse='--no-autostart',
        help='automatically start enabled agents and services')
    agents.add_argument(
        '--no-autostart', action='store_false', dest='autostart',
        help=argparse.SUPPRESS)
    agents.add_argument(
        '--publish-address', metavar='ZMQADDR',
        help='ZeroMQ URL for used for agent publishing')
    agents.add_argument(
        '--subscribe-address', metavar='ZMQADDR',
        help='ZeroMQ URL for used for agent subscriptions')

    control = parser.add_argument_group('control options')
    control.add_argument(
        '--control-socket', metavar='FILE',
        help='path to socket used for control messages')
    control.add_argument(
        '--allow-root', action='store_true', inverse='--no-allow-root',
        help='allow root to connect to control socket')
    control.add_argument(
        '--no-allow-root', action='store_false', dest='allow_root',
        help=argparse.SUPPRESS)
    control.add_argument(
        '--allow-users', action='store_list', metavar='LIST',
        help='users allowed to connect to control socket')
    control.add_argument(
        '--allow-groups', action='store_list', metavar='LIST',
        help='user groups allowed to connect to control socket')

    if HAVE_RESTRICTED:
        class RestrictedAction(argparse.Action):
            def __init__(self, option_strings, dest,
                         const=True, help=None, **kwargs):
                super(RestrictedAction, self).__init__(
                    option_strings, dest=argparse.SUPPRESS, nargs=0,
                    const=const, help=help)
            def __call__(self, parser, namespace, values, option_string=None):
                namespace.verify_agents = self.const
                namespace.resource_monitor = self.const
                namespace.mobility = self.const
        restrict = parser.add_argument_group('restricted options')
        restrict.add_argument(
            '--restricted', action=RestrictedAction, inverse='--no-restricted',
            help='shortcut to enable all restricted features')
        restrict.add_argument(
            '--no-restricted', action=RestrictedAction, const=False,
            help=argparse.SUPPRESS)
        restrict.add_argument(
            '--verify', action='store_true', inverse='--no-verify',
            help='verify agent integrity before execution')
        restrict.add_argument(
            '--no-verify', action='store_false', dest='verify_agents',
            help=argparse.SUPPRESS)
        restrict.add_argument(
            '--resource-monitor', action='store_true',
            inverse='--no-resource-monitor',
            help='enable agent resource management')
        restrict.add_argument(
            '--no-resource-monitor', action='store_false',
            dest='resource_monitor', help=argparse.SUPPRESS)
        restrict.add_argument(
            '--mobility', action='store_true', inverse='--no-mobility',
            help='enable agent mobility')
        restrict.add_argument(
            '--no-mobility', action='store_false', dest='mobility',
            help=argparse.SUPPRESS)
        restrict.add_argument(
            '--mobility-address', metavar='ADDRESS',
            help='specify the address on which to listen')
        restrict.add_argument(
            '--mobility-port', type=int, metavar='NUMBER',
            help='specify the port on which to listen')

    parser.set_defaults(
        log=None,
        log_config=None,
        verboseness=logging.WARNING,
        volttron_home=volttron_home,
        autostart=True,
        publish_address='ipc://$VOLTTRON_HOME/run/publish',
        subscribe_address='ipc://$VOLTTRON_HOME/run/subscribe',
        control_socket='@$VOLTTRON_HOME/run/control',
        allow_root=False,
        allow_users=None,
        allow_groups=None,
        verify_agents=True,
        resource_monitor=True,
        mobility=True,
        mobility_address=None,
        mobility_port=2522
    )

    # Parse and expand options
    args = argv[1:]
    conf = os.path.join(volttron_home, 'config')
    if os.path.exists(conf) and 'SKIP_VOLTTRON_CONFIG' not in os.environ:
        args = ['--config', conf] + args
    opts = parser.parse_args(args)
    opts.control_socket = config.expandall(opts.control_socket)
    opts.publish_address = config.expandall(opts.publish_address)
    opts.subscribe_address = config.expandall(opts.subscribe_address)
    if HAVE_RESTRICTED:
        # Set mobility defaults
        if opts.mobility_address is None:
            info = socket.getaddrinfo(
                None, 0, 0, socket.SOCK_STREAM, 0, socket.AI_NUMERICHOST)
            family = info[0][0] if info else ''
            opts.mobility_address = '::' if family == socket.AF_INET6 else ''
    if getattr(opts, 'show_config', False):
        for name, value in sorted(vars(opts).iteritems()):
            print(name, repr(value))
        return

    # Configure logging
    level = max(1, opts.verboseness)
    if opts.log is None:
        log_to_file(sys.stderr, level)
    elif opts.log == '-':
        log_to_file(sys.stdout, level)
    elif opts.log:
        log_to_file(opts.log, level, handler_class=handlers.WatchedFileHandler)
    else:
        log_to_file(None, 100, handler_class=lambda x: logging.NullHandler())
    if opts.log_config:
        error = configure_logging(opts.log_config)
        if error:
            parser.error('{}: {}'.format(*error))

    # Setup mobility server
    if HAVE_RESTRICTED and opts.mobility:
        ssh_dir = os.path.join(opts.volttron_home, 'ssh')
        try:
            priv_key = RSAKey(filename=os.path.join(ssh_dir, 'id_rsa'))
            authorized_keys = comms.load_authorized_keys(
                os.path.join(ssh_dir, 'authorized_keys'))
        except (OSError, IOError,
                PasswordRequiredException, SSHException) as exc:
            parser.error(exc)

    # Set configuration
    if HAVE_RESTRICTED:
        if opts.verify_agents:
            _log.info('Agent integrity verification enabled')
        if opts.resource_monitor:
            _log.info('Resource monitor enabled')
            opts.resmon = resmon.ResourceMonitor()
    opts.aip = aip.AIPplatform(opts)
    opts.aip.setup()
    if opts.autostart:
        for name, error in opts.aip.autostart():
            _log.error('error starting {!r}: {}\n'.format(name, error))

    # Main loops
    try:
        exchange = gevent.spawn(
            agent_exchange, opts.publish_address, opts.subscribe_address)
        if HAVE_RESTRICTED and opts.mobility:
            address = (opts.mobility_address, opts.mobility_port)
            mobility_in = comms_server.ThreadedServer(
                address, priv_key, authorized_keys, opts.aip)
            mobility_in.start()
            mobility_out = MobilityAgent(
                opts.aip,
                subscribe_address=opts.subscribe_address,
                publish_address=opts.publish_address)
            gevent.spawn(mobility_out.run)
        try:
            control = gevent.spawn(control_loop, opts)
            exchange.link(lambda *a: control.kill())
            control.join()
        finally:
            exchange.kill()
    finally:
        opts.aip.finish()