def test_mix_errors_timeout(self): """Test the result of mixed timeout and error actions.""" action = Action(name='start', target='badname,timeout,localhost', command='/bin/true', timeout=0.9) action.errors = 1 service = Service('test_service') service.add_action(action) service.run('start') self.assertEqual(action.nodes_error(), NodeSet('badname')) self.assertEqual(action.nb_errors(), 1) self.assertEqual(action.nodes_timeout(), NodeSet('timeout')) self.assertEqual(action.nb_timeout(), 1) self.assertEqual(action.status, ERROR) service.reset() action.errors = 2 service.run('start') self.assertEqual(action.nodes_error(), NodeSet('badname')) self.assertEqual(action.nb_errors(), 1) self.assertEqual(action.nodes_timeout(), NodeSet('timeout')) self.assertEqual(action.nb_timeout(), 1) self.assertEqual(action.status, WARNING) service.reset() action.errors = 2 action.warnings = 2 service.run('start') self.assertEqual(action.nodes_error(), NodeSet('badname')) self.assertEqual(action.nb_errors(), 1) self.assertEqual(action.nodes_timeout(), NodeSet('timeout')) self.assertEqual(action.nb_timeout(), 1) self.assertEqual(action.status, DONE)
def reset(self): '''Reset values of attributes in order to perform multiple exec''' Service.reset(self) for service in self._subservices.values(): service.reset() self._sink.reset() self._source.reset()
def test_missing_action(self): """Test prepare with service with missing action is ok""" # Graph leaf has no 'status' action s1 = Service("1") s1.add_action(Action('start', command='/bin/true')) s1.add_action(Action('status', command='/bin/true')) s2 = Service("2") s2.add_action(Action('start', command='/bin/true')) s2.add_dep(s1) s2.run('status') self.assertEqual(s1.status, DONE) self.assertEqual(s2.status, MISSING) s1.reset() s2.reset() self.assertEqual(s1.status, NO_STATUS) self.assertEqual(s2.status, NO_STATUS) # 'status' action is propagated to leaf even if '2' has not the # requested action. s3 = Service("3") s3.add_action(Action('start', command='/bin/true')) s3.add_action(Action('status', command='/bin/true')) s3.add_dep(s2) s3.run('status') self.assertEqual(s1.status, DONE) self.assertEqual(s2.status, MISSING) self.assertEqual(s3.status, DONE)
def test_mix_errors_timeout(self): """Test the result of mixed timeout and error actions.""" # timeout host configuration is in setup_sshconfig (__init__.py) action = Action(name='start', target='badname,timeout,localhost', command='/bin/true', timeout=0.9) action.errors = 1 service = Service('test_service') service.add_action(action) service.run('start') self.assertEqual(action.nodes_error(), NodeSet('badname')) self.assertEqual(action.nb_errors(), 1) self.assertEqual(action.nodes_timeout(), NodeSet('timeout')) self.assertEqual(action.nb_timeout(), 1) self.assertEqual(action.status, ERROR) service.reset() action.errors = 2 service.run('start') self.assertEqual(action.nodes_error(), NodeSet('badname')) self.assertEqual(action.nb_errors(), 1) self.assertEqual(action.nodes_timeout(), NodeSet('timeout')) self.assertEqual(action.nb_timeout(), 1) self.assertEqual(action.status, WARNING) service.reset() action.errors = 2 action.warnings = 2 service.run('start') self.assertEqual(action.nodes_error(), NodeSet('badname')) self.assertEqual(action.nb_errors(), 1) self.assertEqual(action.nodes_timeout(), NodeSet('timeout')) self.assertEqual(action.nb_timeout(), 1) self.assertEqual(action.status, DONE)
def test_mix_errors_timeout(self): """Test the result of mixed timeout and error actions.""" cmd = 'echo "${SSH_CLIENT%%%% *}" | egrep "^(127.0.0.1|::1)$" ||sleep 1' action = Action(name="start", target="badname,%s,localhost" % HOSTNAME, command=cmd, timeout=0.6) action.errors = 1 service = Service("test_service") service.add_action(action) service.run("start") self.assertEqual(action.nb_errors(), 1) self.assertEqual(action.nb_timeout(), 1) self.assertEqual(action.status, ERROR) service.reset() action.errors = 2 service.run("start") self.assertEqual(action.nb_errors(), 1) self.assertEqual(action.nb_timeout(), 1) self.assertEqual(action.status, WARNING) service.reset() action.errors = 2 action.warnings = 2 service.run("start") self.assertEqual(action.nb_errors(), 1) self.assertEqual(action.nb_timeout(), 1) self.assertEqual(action.status, DONE)
def test_reset_service(self): '''Test resest values of a service''' service = Service('brutus') action = Action('start') service.origin = True action.status = DONE service.add_action(action) service._last_action = 'start' service.reset() self.assertFalse(service.origin) self.assertFalse(service._last_action) self.assertEqual(action.status, NO_STATUS) self.assertEqual(service.status, NO_STATUS)
def test_nb_errors_local(self): """Test the method nb_errors() (local)""" service = Service('test_service') act_test = Action(name='test', command='/bin/true') service.add_action(act_test) service.run('test') self.assertEqual(act_test.nodes_error(), NodeSet()) self.assertEqual(act_test.nb_errors(), 0) self.assertEqual(act_test.status, DONE) service.reset() act_test.errors = 1 act_test.command = '/bin/false' service.run('test') self.assertEqual(act_test.nodes_error(), NodeSet("localhost")) self.assertEqual(act_test.nb_errors(), 1) self.assertEqual(act_test.status, WARNING) service.reset() act_test.errors = 1 act_test.warnings = 1 act_test.command = '/bin/false' service.run('test') self.assertEqual(act_test.nodes_error(), NodeSet("localhost")) self.assertEqual(act_test.nb_errors(), 1) self.assertEqual(act_test.status, DONE) service.reset() act_test.errors = 0 service.run('test') self.assertEqual(act_test.nodes_error(), NodeSet("localhost")) self.assertEqual(act_test.nb_errors(), 1) self.assertEqual(act_test.status, ERROR) service.reset() act_test.errors = 0 act_test.warnings = 1 service.run('test') self.assertEqual(act_test.nodes_error(), NodeSet("localhost")) self.assertEqual(act_test.nb_errors(), 1) self.assertEqual(act_test.status, ERROR)
def test_nb_errors_local(self): """Test the method nb_errors() (local)""" service = Service("test_service") act_test = Action(name="test", command="/bin/true") service.add_action(act_test) service.run("test") self.assertEqual(act_test.nb_errors(), 0) self.assertEqual(act_test.status, DONE) service.reset() act_test.errors = 1 act_test.command = "/bin/false" service.run("test") self.assertEqual(act_test.nb_errors(), 1) self.assertEqual(act_test.status, WARNING) service.reset() act_test.errors = 1 act_test.warnings = 1 act_test.command = "/bin/false" service.run("test") self.assertEqual(act_test.nb_errors(), 1) self.assertEqual(act_test.status, DONE) service.reset() act_test.errors = 0 service.run("test") self.assertEqual(act_test.nb_errors(), 1) self.assertEqual(act_test.status, ERROR) service.reset() act_test.errors = 0 act_test.warnings = 1 service.run("test") self.assertEqual(act_test.nb_errors(), 1) self.assertEqual(act_test.status, ERROR)
class ServiceManager(EntityManager): ''' The service manager has to handle call to services. It implements features allowing us to get dependencies of service and so on. ''' def __init__(self): EntityManager.__init__(self) # Variables declared in the global scope self.variables = {} # Top service self.source = Service('root') self.source.simulate = True def __refresh_graph(self, reverse): '''Reinitialize the right values for the graph of services''' for service in self.entities.values(): service.reset() if reverse: self.source.clear_child_deps() else: self.source.clear_parent_deps() def has_service(self, service): '''Determine if the service is registered within the manager''' return service in self.entities.values() def has_warnings(self): '''Determine if the service has one action with the WARNING status''' for ent in self.entities.values(): if ent.status is WARNING: return True return False def add_var(self, varname, value): '''Add a symbol within the service manager''' if varname not in self.variables: self.variables[varname] = value else: raise VariableAlreadyExistError("'%s' already defined" % varname) def remove_var(self, varname): '''Remove var from the the service manager''' if varname in self.variables: del self.variables[varname] def reset(self): '''Clean object service manager.''' self.variables.clear() self.entities.clear() def register_service(self, service): '''Add a new service to the manager.''' assert service, 'service added cannot be None' if service.name in self.entities: raise ServiceAlreadyReferencedError(service.name) else: self.entities[service.name] = service def register_services(self, *args): '''Add all services referenced by args''' for service in args: self.register_service(service) def forget_service(self, service): ''' Remove the service from the manager. It takes care of the relations with others services in order to avoid to get a graph with a bad format. ''' assert service, 'The service that you want to forget cannot be None' if not self.has_service(service): raise ServiceNotFoundError else: dependencies = [] switch = len(service.children) dependencies.extend(service.children.values()) dependencies.extend(service.parents.values()) for dep in dependencies: switch -= 1 if switch > 0: service.remove_dep(dep.target.name, parent=False) else: service.remove_dep(dep.target.name) del self.entities[service.name] def forget_services(self, *args): '''Remove all specified services from the manager''' for service in args: self.forget_service(service) def _variable_config(self, conf): '''Automatic variables based on MilckCheck configuration.''' if conf: # -n NODES self.add_var('SELECTED_NODES', str(conf.get('only_nodes', ''))) # -x NODES self.add_var('EXCLUDED_NODES', str(conf.get('excluded_nodes', ''))) # Add command line variable for defines in conf.get('defines', []): for define in defines.split(): key, value = define.split('=', 1) self.add_var(key, value) else: for varname in ('selected_node', 'excluded_nodes'): self.add_var(varname.upper(), '') def _apply_config(self, conf): ''' This apply a sequence of modifications on the graph. A modification can be an update of the nodes usable by the services or whatever that is referenced within configuration. ''' # Load the configuration located within the directory if conf.get('config_dir'): self.entities.clear() self.load_config(conf['config_dir']) # Avoid some of the services referenced in the graph if conf.get('excluded_svc'): self.__lock_services(conf['excluded_svc']) # Use just those nodes if conf.get('only_nodes') is not None: self.__update_usable_nodes(conf['only_nodes'], 'INT') # Avoid those nodes elif conf.get('excluded_nodes') is not None: self.__update_usable_nodes(conf['excluded_nodes'], 'DIF') def __lock_services(self, services): ''' Lock all services specified in the list. This will assign the LOCKED status on the services. A soon as a service is locked it will never be processed. ''' for service in services: if service in self.entities: self.entities[service].status = LOCKED def _lock_services_except(self, services): """Lock all services except those provided.""" for service in self.entities: if service not in services: self.entities[service].status = LOCKED def __update_usable_nodes(self, nodeset, mode=None): ''' Update target value used by the service and the elements linked to the service. ''' assert mode in (None, 'DIF', 'INT'), \ 'Invalid mode, should be DIF, INT or None' for service in self.entities.values(): service.update_target(nodeset, mode) def call_services(self, services, action, conf=None): '''Allow the user to call one or multiple services.''' assert action, 'action name cannot be None' # Manage reverse mode based on configuration reverse = False if conf: reverse = action in conf.get('reverse_actions') self.variables.clear() # Create global variable from configuration self._variable_config(conf) # Make sure that the graph is usable self.__refresh_graph(reverse) # Apply configuration over the graph if conf: self._apply_config(conf) self.source.reset() # Enable reverse mode if needed self._reverse_mod(reverse) self.source.algo_reversed = reverse if not self.source.has_action(action): self.source.add_action(Action(name=action, command=':')) # Perform all services if not services: for service in self.entities.values(): if reverse and not service.parents: service.add_dep(target=self.source) elif not reverse and not service.children: self.source.add_dep(target=service) # Perform required services else: for service_name in services: if service_name in self.entities: if reverse: self.entities[service_name].add_dep(target=self.source) else: self.source.add_dep(target=self.entities[service_name]) else: raise ServiceNotFoundError('Undefined service [%s]' % service_name) if conf and conf.get('nodeps'): self._lock_services_except(self.source.deps().keys()) self.source.run(action) def output_graph(self, services=None, excluded=None): """Return entities graph (DOT format)""" grph = "digraph dependency {\n" grph += "compound=true;\n" #grph += "node [shape=circle];\n" grph += "node [style=filled];\n" for service in (services or self.entities): if not self.entities[service].excluded(excluded): grph += self.entities[service].graph(excluded) grph += '}\n' return grph def load_config(self, conf): ''' Load the configuration within the manager thanks to MilkCheckConfig ''' from MilkCheck.Config.Configuration import MilkCheckConfig config = MilkCheckConfig() config.load_from_dir(directory=conf) config.build_graph()
class ServiceGroup(Service): """ This class models a group of service. A group of service can own actions it's no mandatory. However it shall have subservices """ def __init__(self, name, target=None): Service.__init__(self, name, target) # Entry point of the group self._source = Service('source') self._source.simulate = True self._source.add_dep(target=self, parent=False) del self.parents['source'] self._sink = Service('sink') self._sink.simulate = True # subservices self._subservices = {} def update_target(self, nodeset, mode=None): '''Update the attribute target of a ServiceGroup''' BaseEntity.update_target(self, nodeset, mode) for service in self._subservices.values(): service.update_target(nodeset, mode) def filter_nodes(self, nodes): """ Add error nodes to skip list. Nodes in this list will not be used when launching actions. """ BaseEntity.filter_nodes(self, nodes) for service in self._subservices.values(): service.filter_nodes(nodes) def iter_subservices(self): '''Return an iterator over the subservices''' return self._subservices.itervalues() def reset(self): '''Reset values of attributes in order to perform multiple exec''' Service.reset(self) for service in self._subservices.values(): service.reset() self._sink.reset() self._source.reset() def search(self, name, reverse=False): """Look for a node through the overall graph""" target = None if reverse: target = self._sink.search(name, reverse) else: target = self._source.search(name) if target: return target else: return Service.search(self, name, reverse) def has_subservice(self, name): """ Check if the service is referenced within the group """ return name in self._subservices def has_action(self, action_name): """ A group consider to get an action only if both source and sink own the action """ for svc in self.iter_subservices(): if svc.has_action(action_name): return True return False def skip(self): """Skip all services from this group""" for svc in self.iter_subservices(): svc.skip() def to_skip(self, action): """ Tell if group should be skipped for provided action name. That means that all its subservices should be skipped. """ for svc in self._subservices.values(): if not svc.to_skip(action): return False return True def add_inter_dep(self, target, base=None, sgth=REQUIRE): """ Add dependency in the subgraph. Adding a dependency in using this method means that the target will be an internal dependency of the group """ if base and not self.has_subservice(base.name): raise ServiceNotFoundError() # A base node is specified so hook target on it if base: if not target.has_parent_dep('sink'): target.add_dep(target=self._sink) if not target.children: target.add_dep(target=self._source, parent=False) base.add_dep(target=target, sgth=sgth) # Target is hooked on source and sink else: self._sink.add_dep(target=target, sgth=sgth, parent=False) self._source.add_dep(target=target, sgth=sgth) self._subservices[target.name] = target target.parent = self self.__update_edges() def __update_edges(self, create_links=False): '''Update edges of the subgraph''' for abs_ser in (self._source, self._sink): if create_links: for service in self._subservices.values(): if not service.parents: self._sink.add_dep(target=service, parent=False) if not service.children: self._source.add_dep(target=service) else: if abs_ser is self._source: deps = abs_ser.parents else: deps = abs_ser.children for dep in deps.values(): # If direct dependency of source have more than one child # remove this dependency if abs_ser is self._source and len(dep.target.children) > 1: dep.target.remove_dep('source', parent=False) # If direct dependency of source have more than one parent # remove this dependency elif abs_ser is self._sink and len(dep.target.parents) > 1: dep.target.remove_dep('sink') def remove_inter_dep(self, dep_name): """ Remove a dependency on both side, in the current object and in the target object concerned by the dependency """ if not self.has_subservice(dep_name): raise ServiceNotFoundError() else: for dep in self._subservices[dep_name].parents.values(): dep.target.remove_dep(dep_name, parent=False) for dep in self._subservices[dep_name].children.values(): dep.target.remove_dep(dep_name) del self._subservices[dep_name] self.__update_edges(True) def graph_info(self): """ Return a tuple to manage dependencies output """ return ("%s.__hook" % self.fullname(), "cluster_%s" % self.fullname()) def graph(self, excluded=None): """ Generate a subgraph of dependencies in the ServiceGroup""" grph = '' grph += 'subgraph "cluster_%s" {\nlabel="%s";\n' % (self.fullname(), self.fullname()) grph += 'style=rounded;\nnode [style=filled];\n' # Create a default node to manage DOT output # __hook will be used to attach the nodes to the subgraph grph += '"%s.__hook" [style=invis];\n' % self.fullname() # Graph content of the servicegroup entities = self._subservices for ent in entities.values(): if not ent.excluded(excluded): grph += ent.graph(excluded=excluded) grph += '}\n' # Graph dependencies of the service group for dep in self.deps().values(): if not dep.target.excluded(excluded): if not dep.target.simulate: grph += dep.graph(self) return grph def eval_deps_status(self): """ Evaluate the result of the dependencies in order to check if we have to continue in normal mode or in a degraded mode. """ extd_status = Service.eval_deps_status(self) intd_status = DONE if self._algo_reversed: intd_status = self._sink.eval_deps_status() else: intd_status = self._source.eval_deps_status() if DEP_ORDER[extd_status] > DEP_ORDER[intd_status]: return extd_status else: return intd_status def _launch_action(self, action, status): """ ServiceGroup does not have real action, but internal services instead. Launch internal services if needed or just set group status. Group status is based on its internal status. """ # Check if the action is MISSING in the whole group. if not self.has_action(action): self.update_status(MISSING) # Check if the whole group is SKIPPED elif self.to_skip(action): self.update_status(SKIPPED) # If there is a dep error, we should not run anything elif status == DEP_ERROR: self.update_status(DEP_ERROR) # No dep error, try to run internal services elif self._algo_reversed and self._sink.children and \ self._sink.status is NO_STATUS: self._sink.prepare(action) elif not self._algo_reversed and self._source.parents and \ self._source.status is NO_STATUS: self._source.prepare(action) # No service to run, just update status else: if self._algo_reversed: intd_status = self._sink.eval_deps_status() else: intd_status = self._source.eval_deps_status() self.update_status(intd_status) def inherits_from(self, entity): '''Inherit properties from entity''' BaseEntity.inherits_from(self, entity) for subservice in self.iter_subservices(): subservice.inherits_from(self) def set_algo_reversed(self, flag): """Updates dependencies if reversed flag is specified""" if self._algo_reversed and not flag: del self._sink.parents[self.name] self._source.add_dep(target=self, parent=False) del self.parents['source'] elif not self._algo_reversed and flag: del self._source.children[self.name] self._sink.add_dep(target=self) del self.children['sink'] for service in self._subservices.values(): service.algo_reversed = flag self._algo_reversed = flag self._sink._algo_reversed = flag self._source._algo_reversed = flag algo_reversed = property(fset=set_algo_reversed) def fromdict(self, grpdict): """Populate group attributes from dict.""" BaseEntity.fromdict(self, grpdict) if 'services' in grpdict: dep_mapping = {} # Wrap dependencies from YAML and build the service for names, props in grpdict['services'].items(): for subservice in NodeSet(names): # Parsing dependencies wrap = DepWrapper() for prop in ('require', 'require_weak', 'require_filter', 'filter', 'before', 'after', 'check'): if prop in props: if prop in ('before', 'after'): props['require_weak'] = props[prop] prop = 'require_weak' # Only for compat with v1.1beta versions if prop == 'require_filter': props['filter'] = props[prop] prop = 'filter' wrap.deps[prop] = props[prop] # Get subservices which might be Service or ServiceGroup service = None if 'services' in props: service = ServiceGroup(subservice) else: service = Service(subservice) # Link the group and its new subservice together self._subservices[subservice] = service service.parent = self # Populate based on dict content service.fromdict(props) wrap.source = service dep_mapping[subservice] = wrap # Generate dependency links of the service for wrap in dep_mapping.values(): # Not any dependencies so just attach for dtype in wrap.deps: wrap.deps[dtype] = wrap.source._resolve(wrap.deps[dtype]) # For simplicity, supports deps as a single service if type(wrap.deps[dtype]) is str: wrap.deps[dtype] = [wrap.deps[dtype]] for dep in wrap.deps[dtype]: if dep not in self._subservices: raise UnknownDependencyError(dep) wrap.source.add_dep(self._subservices[dep], sgth=dtype.upper()) # Bind subgraph to the service group for service in self.iter_subservices(): if not service.children: service.add_dep(self._source, parent=False) if not service.parents: service.add_dep(self._sink) for subser in self.iter_subservices(): subser.inherits_from(self) def resolve_all(self): """Resolve all variables in ServiceGroup properties""" BaseEntity.resolve_all(self) for subser in self.iter_subservices(): subser.resolve_all()
class ServiceGroup(Service): """ This class models a group of service. A group of service can own actions it's no mandatory. However it shall have subservices """ def __init__(self, name, target=None): Service.__init__(self, name, target) # Entry point of the group self._source = Service('source') self._source.simulate = True self._source.add_dep(target=self, parent=False) del self.parents['source'] self._sink = Service('sink') self._sink.simulate = True # subservices self._subservices = {} def update_target(self, nodeset, mode=None): '''Update the attribute target of a ServiceGroup''' BaseEntity.update_target(self, nodeset, mode) for service in self._subservices.values(): service.update_target(nodeset, mode) def iter_subservices(self): '''Return an iterator over the subservices''' return self._subservices.itervalues() def reset(self): '''Reset values of attributes in order to perform multiple exec''' Service.reset(self) for service in self._subservices.values(): service.reset() self._sink.reset() self._source.reset() def search(self, name, reverse=False): """Look for a node through the overall graph""" target = None if reverse: target = self._sink.search(name, reverse) else: target = self._source.search(name) if target: return target else: return Service.search(self, name, reverse) def has_subservice(self, name): """ Check if the service is referenced within the group """ return name in self._subservices def has_action(self, action_name): """ A group consider to get an action only if both source and sink own the action """ for svc in self.iter_subservices(): if svc.has_action(action_name): return True return False def to_skip(self, action): """ Tell if group should be skipped for provided action name. That means that all its subservices should be skipped. """ for svc in self._subservices.values(): if not svc.to_skip(action): return False return True def add_inter_dep(self, target, base=None, sgth=REQUIRE): """ Add dependency in the subgraph. Adding a dependency in using this method means that the target will be an internal dependency of the group """ if base and not self.has_subservice(base.name): raise ServiceNotFoundError() # A base node is specified so hook target on it if base: if not target.has_parent_dep('sink'): target.add_dep(target=self._sink) if not target.children: target.add_dep(target=self._source, parent=False) base.add_dep(target=target, sgth=sgth) # Target is hooked on source and sink else: self._sink.add_dep(target=target, sgth=sgth, parent=False) self._source.add_dep(target=target, sgth=sgth) self._subservices[target.name] = target target.parent = self self.__update_edges() def __update_edges(self, create_links=False): '''Update edges of the subgraph''' for abs_ser in (self._source, self._sink): if create_links: for service in self._subservices.values(): if not service.parents: self._sink.add_dep(target=service, parent=False) if not service.children: self._source.add_dep(target=service) else: if abs_ser is self._source: deps = abs_ser.parents else: deps = abs_ser.children for dep in deps.values(): # If direct dependency of source have more than one child # remove this dependency if abs_ser is self._source and len(dep.target.children) > 1: dep.target.remove_dep('source', parent=False) # If direct dependency of source have more than one parent # remove this dependency elif abs_ser is self._sink and len(dep.target.parents) > 1: dep.target.remove_dep('sink') def remove_inter_dep(self, dep_name): """ Remove a dependency on both side, in the current object and in the target object concerned by the dependency """ if not self.has_subservice(dep_name): raise ServiceNotFoundError() else: for dep in self._subservices[dep_name].parents.values(): dep.target.remove_dep(dep_name, parent=False) for dep in self._subservices[dep_name].children.values(): dep.target.remove_dep(dep_name) del self._subservices[dep_name] self.__update_edges(True) def graph_info(self): """ Return a tuple to manage dependencies output """ return ("%s.__hook" % self.fullname(), "cluster_%s" % self.fullname()) def graph(self, excluded=None): """ Generate a subgraph of dependencies in the ServiceGroup""" grph = '' grph += 'subgraph "cluster_%s" {\nlabel="%s";\n' % (self.fullname(), self.fullname()) grph += 'style=rounded;\nnode [style=filled];\n' # Create a default node to manage DOT output # __hook will be used to attach the nodes to the subgraph grph += '"%s.__hook" [style=invis];\n' % self.fullname() # Graph content of the servicegroup entities = self._subservices for ent in entities.values(): if not ent.excluded(excluded): grph += ent.graph(excluded=excluded) grph += '}\n' # Graph dependencies of the service group for dep in self.deps().values(): if not dep.target.excluded(excluded): if not dep.target.simulate: grph += dep.graph(self) return grph def eval_deps_status(self): """ Evaluate the result of the dependencies in order to check if we have to continue in normal mode or in a degraded mode. """ extd_status = Service.eval_deps_status(self) intd_status = DONE if self._algo_reversed: intd_status = self._sink.eval_deps_status() else: intd_status = self._source.eval_deps_status() if DEP_ORDER[extd_status] > DEP_ORDER[intd_status]: return extd_status else: return intd_status def _launch_action(self, action, status): """ ServiceGroup does not have real action, but internal services instead. Launch internal services if needed or just set group status. Group status is based on its internal status. """ # Check if the action is MISSING in the whole group. if not self.has_action(action): self.update_status(MISSING) # Check if the whole group is SKIPPED elif self.to_skip(action): self.update_status(SKIPPED) # If there is a dep error, we should not run anything elif status == DEP_ERROR: self.update_status(DEP_ERROR) # No dep error, try to run internal services elif self._algo_reversed and self._sink.children and \ self._sink.status is NO_STATUS: self._sink.prepare(action) elif not self._algo_reversed and self._source.parents and \ self._source.status is NO_STATUS: self._source.prepare(action) # No service to run, just update status else: if self._algo_reversed: intd_status = self._sink.eval_deps_status() else: intd_status = self._source.eval_deps_status() self.update_status(intd_status) def inherits_from(self, entity): '''Inherit properties from entity''' BaseEntity.inherits_from(self, entity) for subservice in self.iter_subservices(): subservice.inherits_from(self) def set_algo_reversed(self, flag): """Updates dependencies if reversed flag is specified""" if self._algo_reversed and not flag: del self._sink.parents[self.name] self._source.add_dep(target=self, parent=False) del self.parents['source'] elif not self._algo_reversed and flag: del self._source.children[self.name] self._sink.add_dep(target=self) del self.children['sink'] for service in self._subservices.values(): service.algo_reversed = flag self._algo_reversed = flag self._sink._algo_reversed = flag self._source._algo_reversed = flag algo_reversed = property(fset=set_algo_reversed) def fromdict(self, grpdict): """Populate group attributes from dict.""" BaseEntity.fromdict(self, grpdict) if 'services' in grpdict: dep_mapping = {} # Wrap dependencies from YAML and build the service for names, props in grpdict['services'].items(): for subservice in NodeSet(names): # Parsing dependencies wrap = DepWrapper() for prop in ('require', 'require_weak', 'before', 'after', 'check'): if prop in props: if prop in ('before', 'after'): props['require_weak'] = props[prop] prop = 'require_weak' wrap.deps[prop] = props[prop] # Get subservices which might be Service or ServiceGroup service = None if 'services' in props: service = ServiceGroup(subservice) service.fromdict(props) else: service = Service(subservice) service.fromdict(props) # Link the group and its new subservice together self._subservices[subservice] = service service.parent = self wrap.source = service dep_mapping[subservice] = wrap # Generate dependency links of the service for wrap in dep_mapping.values(): # Not any dependencies so just attach for dtype in wrap.deps: for dep in wrap.deps[dtype]: if dep not in self._subservices: raise UnknownDependencyError(dep) wrap.source.add_dep(self._subservices[dep], sgth=dtype.upper()) # Bind subgraph to the service group for service in self.iter_subservices(): if not service.children: service.add_dep(self._source, parent=False) if not service.parents: service.add_dep(self._sink) for subser in self.iter_subservices(): subser.inherits_from(self)