Exemple #1
0
class ServicesTest(TestCase):
    def setUp(self):
        self.manager = ServiceManager(MagicMock())
        self.manager.add('test1', FakeService())
        self.manager.add('test2', FakeService())

    async def test_001_start_stop_all(self):
        await self.manager.start()
        assert_true(self.manager.get('test1').started)
        assert_true(self.manager.get('test2').started)

        # Add one on the way
        self.manager.add('test3', FakeService())
        # finish coroutines
        await exhaust_callbacks(self.loop)
        assert_true(self.manager.get('test3').started)

        await self.manager.stop()
        assert_false(self.manager.get('test1').started)
        assert_false(self.manager.get('test2').started)
        assert_false(self.manager.get('test3').started)
Exemple #2
0
class ServicesTest(TestCase):

    def setUp(self):
        self.manager = ServiceManager(MagicMock())
        self.manager.add('test1', FakeService())
        self.manager.add('test2', FakeService())

    async def test_001_start_stop_all(self):
        await self.manager.start()
        assert_true(self.manager.get('test1').started)
        assert_true(self.manager.get('test2').started)

        # Add one on the way
        self.manager.add('test3', FakeService())
        # finish coroutines
        await exhaust_callbacks(self.loop)
        assert_true(self.manager.get('test3').started)

        await self.manager.stop()
        assert_false(self.manager.get('test1').started)
        assert_false(self.manager.get('test2').started)
        assert_false(self.manager.get('test3').started)
Exemple #3
0
    def __init__(self, **kwargs):
        # List of configuration schemas
        self._schemas = []

        # Get configuration from multiple sources and register base schema
        kwargs = kwargs or get_command_kwargs()
        self.config_filename = kwargs.get('config', DEFAULT_CONF_FILE)
        self._config = get_full_config(**kwargs)
        self.register_schema(self.BASE_CONF_SCHEMA)

        # Initialize logging
        logging.config.dictConfig(self._config['log'])

        self.loop = asyncio.get_event_loop() or asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        self._services = ServiceManager(self)
        self._services.add('api', Exposer(self))

        # Add bus service if in conf file
        if self._config.get('bus') is not None:
            self._services.add('bus', Bus(self))

        self.is_stopping = False
Exemple #4
0
class Nyuki(metaclass=CapabilityHandler):

    """
    A lightweigh base class to build nyukis. A nyuki provides tools that shall
    help the developer with managing the following topics:
      - Bus of communication between nyukis.
      - Asynchronous events.
      - Capabilities exposure through a REST API.
    This class has been written to perform the features above in a reliable,
    single-threaded, asynchronous and concurrent-safe environment.
    The core engine of a nyuki implementation is the asyncio event loop
    (a single loop is used for all features).
    A wrapper is also provide to ease the use of asynchronous calls
    over the actions nyukis are inteded to do.
    """

    # Configuration schema must follow jsonschema rules.
    BASE_CONF_SCHEMA = {
        "type": "object",
        "required": ["log"]
    }

    def __init__(self, **kwargs):
        # List of configuration schemas
        self._schemas = []

        # Get configuration from multiple sources and register base schema
        kwargs = kwargs or get_command_kwargs()
        self.config_filename = kwargs.get('config', DEFAULT_CONF_FILE)
        self._config = get_full_config(**kwargs)
        self.register_schema(self.BASE_CONF_SCHEMA)

        # Initialize logging
        logging.config.dictConfig(self._config['log'])

        self.loop = asyncio.get_event_loop() or asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        self._services = ServiceManager(self)
        self._services.add('api', Exposer(self))

        # Add bus service if in conf file
        if self._config.get('bus') is not None:
            self._services.add('bus', Bus(self))

        self.is_stopping = False

    def __getattribute__(self, name):
        """
        Getattr, or returns the specified service
        """
        try:
            return super().__getattribute__(name)
        except AttributeError as exc:
            try:
                return self._services.get(name)
            except KeyError:
                raise exc

    @property
    def config(self):
        return self._config

    def start(self):
        """
        Start the nyuki
        The nyuki process is terminated when this method is finished
        """
        self.loop.add_signal_handler(SIGTERM, self.abort, SIGTERM)
        self.loop.add_signal_handler(SIGINT, self.abort, SIGINT)

        # Configure services with nyuki's configuration
        log.debug('Running configure for services')
        for name, service in self._services.all.items():
            service.configure(**self._config.get(name, {}))
        log.debug('Done configuring')

        # Start services
        self.loop.run_until_complete(self._services.start())

        # Call for setup
        if not asyncio.iscoroutinefunction(self.setup):
            log.warning('setup method must be a coroutine')
            self.setup = asyncio.coroutine(self.setup)
        self.loop.run_until_complete(self.setup())

        # Main loop
        self.loop.run_forever()

        # Call for teardown
        if not asyncio.iscoroutinefunction(self.teardown):
            log.warning('teardown method must be a coroutine')
            self.teardown = asyncio.coroutine(self.teardown)
        self.loop.run_until_complete(self.teardown())

        # Close everything : terminates nyuki
        self.loop.close()

    def _stop_loop(self):
        """
        Call the loop to stop itself.
        """
        self.loop.call_soon_threadsafe(self.loop.stop)

    def abort(self, signal):
        """
        Signal handler: gracefully stop the nyuki.
        """
        log.warning('Caught signal %d, stopping nyuki', signal)
        asyncio.ensure_future(self.stop())

    async def stop(self, timeout=5):
        """
        Stop the nyuki
        """
        if self.is_stopping:
            log.warning('Force closing the nyuki')
            self._stop_loop()
            return

        self.is_stopping = True
        await self._services.stop()
        self._stop_loop()

    def register_schema(self, schema, format_checker=None):
        """
        Add a jsonschema to validate on configuration update.
        """
        self._schemas.append((schema, format_checker))
        self._validate_config()

    def _validate_config(self, config=None):
        """
        Validate on all registered configuration schemas.
        """
        log.debug('Validating configuration')
        config = config or self._config
        for schema, checker in self._schemas:
            validate(config, schema, format_checker=checker)

    async def setup(self):
        """
        First thing called when starting the event loop, coroutine or not.
        """
        log.warning('Setup called, but not overridden')

    async def reload(self):
        """
        Called when the configuration is modified
        """
        log.warning('Reload called, but not overridden')

    async def teardown(self):
        """
        Called right before closing the event loop, stopping the Nyuki.
        """
        log.warning('Teardown called, but not overridden')

    def update_config(self, *new_confs):
        """
        Update the current configuration with the given list of dicts.
        """
        config = merge_configs(self._config, *new_confs)
        self._validate_config(config)
        self._config = config

    def save_config(self):
        """
        Save the current configuration dict to its JSON file.
        """
        write_conf_json(self.config, self.config_filename)

    async def _reload_config(self, request):
        """
        Reload the configuration and the services
        """
        self.save_config()
        logging.config.dictConfig(self._config['log'])
        await self.reload()
        for name, service in self._services.all.items():
            if name in request:
                await service.stop()
                service.configure(**self._config[name])
                asyncio.ensure_future(service.start())

    @resource(endpoint='/config', version='v1')
    class Configuration:

        def get(self, request):
            return Response(self._config)

        async def patch(self, request):
            try:
                self.update_config(request)
            except ValidationError as error:
                error = {'error': error.message}
                log.error('Bad configuration received : {}'.format(request))
                log.debug(error)
                return Response(body=error, status=400)

            # Reload what is necessary, return the http response immediately
            asyncio.ensure_future(self._reload_config(request))

            return Response(self._config)

    @resource(endpoint='/swagger', version='v1')
    class Swagger:

        def get(self, request):
            try:
                with open('swagger.json', 'r') as f:
                    body = json.loads(f.read())
            except OSError:
                return Response(status=404, body={
                    'error': 'Missing swagger documentation'
                })

            return Response(body=body)
Exemple #5
0
 def setUp(self):
     self.manager = ServiceManager(MagicMock())
     self.manager.add('test1', FakeService())
     self.manager.add('test2', FakeService())
Exemple #6
0
 def setUp(self):
     self.manager = ServiceManager(MagicMock())
     self.manager.add('test1', FakeService())
     self.manager.add('test2', FakeService())