class ControllerRole: """Mixin class for enabling `controller` role on service.""" auto_register_on_discovery = False master = None @method_decorator(retry(max_retries=0, delay=3, exception_types=(RequestError,), on_exception=_cannot_register_on_master)) async def register(self): kwargs = dict(controller='game_controller', metadata=self.app.metadata) await self.internal_request(self.master, 'register_controller', **kwargs) logger.info('Registered on master: %s' % self.master) @staticmethod def setup_internal_api(): @as_internal() async def heartbeat_report(api, **options): return { 'memory': virtual_memory().percent, 'cpu': cpu_percent() } def setup(self): self.setup_internal_api() super().setup() async def on_start(self): await super().on_start() await self.register()
class AdminService(PlainService): update_services_meta_period = 5 update_services_meta = True def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.update_services_meta: self.services_meta_updater = PeriodicCallback( self.set_services_meta, self.update_services_meta_period * 1000) else: self.services_meta_updater = None def setup(self) -> None: self.settings.update(services_meta={}) self.settings.update(services_all_meta={}) super().setup() async def get_services_metadata(self, exclude_names=None): services_metadata = [] exclude_names = exclude_names or [] try: services_names = await self.discovery_request('get_services_names') except RequestTimeoutError: pass # ¯\_(ツ)_/¯ else: for name in services_names: if name in exclude_names: continue try: metadata = await self.internal_request(name, method='get_service_metadata') services_metadata.append(metadata) except RequestTimeoutError: pass # ¯\_(ツ)_/¯ return services_metadata @method_decorator(retry(max_retries=0, delay=3, exception_types=(RequestError,), on_exception=lambda func, e: logger.error('Cannot get services meta. Retry...'), )) async def set_services_meta(self): services_meta = await self.get_services_metadata(exclude_names=[self.name]) services_all_meta = services_meta + [self.app.metadata] self.settings.update(services_meta=services_meta) self.settings.update(services_all_meta=services_all_meta) async def on_start(self) -> None: await super().on_start() await self.set_services_meta() if self.services_meta_updater is not None: self.services_meta_updater.start() async def on_stop(self) -> None: if self.services_meta_updater is not None: self.services_meta_updater.stop() await super().on_stop()
class DiscoveryService(BaseService): cleanup_storage_on_start = True cleanup_services_period = 5 ping_services = True ping_max_retries = 1 ping_timeout = 1 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.ping_services: self.ping_monitor = PeriodicCallback( self.check_services, self.cleanup_services_period * 1000) else: self.ping_monitor = None self.registry = self.app.registry self.storage = caches['services'] async def on_start(self) -> None: await super().on_start() await self.setup_services(cleanup=self.cleanup_storage_on_start) if self.ping_monitor is not None: self.ping_monitor.start() async def on_stop(self) -> None: await super().on_stop() if self.ping_monitor is not None: self.ping_monitor.stop() @method_decorator(retry(max_retries=ping_max_retries, delay=0, exception_types=(RequestTimeoutError, KeyError, TypeError))) async def is_service_alive(self, name): internal_request = partial(self.internal_request, name) try: response = await internal_request('ping', timeout=self.ping_timeout) return response['message'] == 'pong' except Exception as e: logger.error('Service `%s` is unreachable. %s' % (name, str(e))) raise async def check_services(self): for name in self.registry.keys(): if name == self.name: # Prevent self pinging continue if not await self.is_service_alive(name): await self.remove_service(name) else: if name not in await self.list_services(): await self.setup_service(name, self.registry[name]) async def setup_services(self, cleanup=False) -> None: if cleanup: await self.remove_services() self.storage.set_many(self.registry, timeout=None) async def remove_services(self) -> None: self.storage.delete_many(keys=self.registry.keys()) async def list_services(self) -> list: """Returns a list of services names.""" return list(self.storage.get_many(keys=self.registry.keys()).keys()) async def setup_service(self, name: str, networks: dict) -> None: self.storage.set(name, networks, timeout=None) async def remove_service(self, name: str) -> None: self.storage.delete(name) async def is_service_exists(self, name: str) -> bool: return name in self.storage async def get_service(self, name: str, networks: list = None) -> dict: if not await self.is_service_exists(name): raise ServiceDoesNotExist(name) return dict_filter(self.storage.get(name), keys=networks) async def get_services(self) -> dict: return self.storage.get_many(keys=self.registry.keys())
class PlainService(BaseService): auto_register_on_discovery = True discovery_name = 'discovery' message_name = 'message' admin_name = 'admin' def __init__(self, handlers=None, default_host=None, transforms=None, **kwargs): super().__init__(handlers, default_host, transforms, **kwargs) self.messenger_client = None def setup(self) -> None: super().setup() self.settings.update(messenger_url=None) self.settings.update(registered_services={}) self.setup_internal_request_methods() # noinspection PyAttributeOutsideInit def setup_internal_request_methods(self): self.discovery_request = partial(self.internal_request, self.discovery_name) self.message_request = partial(self.internal_request, self.message_name) self.admin_request = partial(self.internal_request, self.admin_name) @method_decorator(retry(max_retries=0, delay=3, exception_types=(RequestError,), on_exception=lambda func, e: logger.error('Service `discovery` is unreachable.'), )) async def register_on_discovery(self) -> None: kwargs = dict(name=self.name, networks=self.app.registry_entry) await self.discovery_request('set_service_bulk', **kwargs) logger.info('Connected to `discovery` service.') async def unregister_on_discovery(self) -> None: await self.discovery_request('remove_service', name=self.name) logger.info('Disconnected from `discovery` service.') async def discover(self, name: str, network: str = None) -> dict: return await self.discovery_request('get_service', name=name, network=network) @method_decorator(retry(max_retries=0, delay=3, exception_types=(RequestError,), on_exception=lambda func, e: logger.error('Cannot get registered services. Retry...'), )) async def set_registered_services(self) -> None: registered_services = await self.discovery_request('get_registered_services') self.settings.update(registered_services=registered_services) @method_decorator(retry(max_retries=0, delay=3, exception_types=(RequestError,), on_exception=lambda func, e: logger.error('Cannot get login url. Retry...'), )) async def set_login_url(self) -> None: login_url = await self.admin_request('get_login_url') self.settings.update(login_url=login_url) logger.debug('Login url: %s' % login_url) def create_messenger_client(self) -> MessengerClient: messenger_url = self.get_service_location(self.message_name, 'internal') return MessengerClient(url=messenger_url) @method_decorator(retry(max_retries=0, delay=3, exception_types=(ConnectionError,), on_exception=lambda func, e: logger.error('Cannot connect to messenger. Retry...'), )) async def messenger_client_connect(self) -> None: self.messenger_client = self.create_messenger_client() await self.messenger_client.connect() def get_service_location(self, service_name, network): return self.settings['registered_services'][service_name][network] async def on_start(self) -> None: await super().on_start() if self.auto_register_on_discovery: await self.register_on_discovery() await self.set_registered_services() await self.set_login_url() await self.messenger_client_connect() async def on_stop(self) -> None: self.messenger_client.close() if self.auto_register_on_discovery: await self.unregister_on_discovery() await super().on_stop()