From 68b31a09b4012dd33d9cbe24d45057ef6822aeea Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Mon, 2 Apr 2018 15:11:27 -0400 Subject: [PATCH] add daemon Component and ComponentManager classes --- lbrynet/daemon/Component.py | 57 ++++++++++++++++++ lbrynet/daemon/ComponentManager.py | 93 ++++++++++++++++++++++++++++++ lbrynet/daemon/auth/server.py | 2 + 3 files changed, 152 insertions(+) create mode 100644 lbrynet/daemon/Component.py create mode 100644 lbrynet/daemon/ComponentManager.py diff --git a/lbrynet/daemon/Component.py b/lbrynet/daemon/Component.py new file mode 100644 index 000000000..2b6ac0582 --- /dev/null +++ b/lbrynet/daemon/Component.py @@ -0,0 +1,57 @@ +import logging +from twisted.internet import defer +from ComponentManager import ComponentManager + +log = logging.getLogger(__name__) + + +class ComponentType(type): + def __new__(mcs, name, bases, newattrs): + klass = type.__new__(mcs, name, bases, newattrs) + if name != "Component": + ComponentManager.components.add(klass) + return klass + + +class Component(object): + """ + lbrynet-daemon component helper + + Inheriting classes will be automatically registered with the ComponentManager and must implement setup and stop + methods + """ + + __metaclass__ = ComponentType + depends_on = [] + component_name = None + running = False + + @classmethod + def setup(cls): + raise NotImplementedError() # override + + @classmethod + def stop(cls): + raise NotImplementedError() # override + + @classmethod + @defer.inlineCallbacks + def _setup(cls): + try: + result = yield defer.maybeDeferred(cls.setup) + cls.running = True + defer.returnValue(result) + except Exception as err: + log.exception("Error setting up %s", cls.component_name or cls.__name__) + raise err + + @classmethod + @defer.inlineCallbacks + def _stop(cls): + try: + result = yield defer.maybeDeferred(cls.stop) + cls.running = False + defer.returnValue(result) + except Exception as err: + log.exception("Error stopping %s", cls.__name__) + raise err diff --git a/lbrynet/daemon/ComponentManager.py b/lbrynet/daemon/ComponentManager.py new file mode 100644 index 000000000..8b645c014 --- /dev/null +++ b/lbrynet/daemon/ComponentManager.py @@ -0,0 +1,93 @@ +import logging +from twisted.internet import defer + +log = logging.getLogger(__name__) + + +class ComponentManager(object): + components = set() + + @classmethod + def sort_components(cls, reverse=False): + """ + Sort components by requirements + """ + steps = [] + staged = set() + components = set(cls.components) + + # components with no requirements + step = [] + for component in set(components): + if not component.depends_on: + step.append(component) + staged.add(component.component_name) + components.remove(component) + + if step: + steps.append(step) + + while components: + step = [] + to_stage = set() + for component in set(components): + reqs_met = 0 + for needed in component.depends_on: + if needed in staged: + reqs_met += 1 + if reqs_met == len(component.depends_on): + step.append(component) + to_stage.add(component.component_name) + components.remove(component) + if step: + staged.update(to_stage) + steps.append(step) + elif components: + raise SyntaxError("components cannot be started: %s" % components) + if reverse: + steps.reverse() + return steps + + @classmethod + @defer.inlineCallbacks + def setup(cls): + """ + Start Components in sequence sorted by requirements + + :return: (defer.Deferred) + """ + stages = cls.sort_components() + for stage in stages: + yield defer.DeferredList([component._setup() for component in stage]) + + @classmethod + @defer.inlineCallbacks + def stop(cls): + """ + Stop Components in reversed startup order + + :return: (defer.Deferred) + """ + stages = cls.sort_components(reverse=True) + for stage in stages: + yield defer.DeferredList([component._stop() for component in stage]) + + @classmethod + def all_components_running(cls, *component_names): + """ + :return: (bool) True if all specified components are running + """ + c = {component.component_name: component for component in cls.components} + for component in component_names: + if component not in c: + raise NameError("%s is not a known Component" % component) + if not c[component].running: + return False + return True + + @classmethod + def get_component(cls, component_name): + for component in cls.components: + if component.component_name == component_name: + return component + raise NameError(component_name) diff --git a/lbrynet/daemon/auth/server.py b/lbrynet/daemon/auth/server.py index a0d365a35..009df762a 100644 --- a/lbrynet/daemon/auth/server.py +++ b/lbrynet/daemon/auth/server.py @@ -17,6 +17,7 @@ from lbrynet.core.Error import InvalidAuthenticationToken from lbrynet.core import utils from lbrynet.daemon.auth.util import APIKey, get_auth_message from lbrynet.daemon.auth.client import LBRY_SECRET +from lbrynet.daemon.Component import ComponentManager from lbrynet.undecorated import undecorated log = logging.getLogger(__name__) @@ -132,6 +133,7 @@ class JSONRPCServerType(type): class AuthorizedBase(object): __metaclass__ = JSONRPCServerType + component_manager = ComponentManager @staticmethod def deprecated(new_command=None):