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 test_search_node_graph(self): """Test search node in a graph trough a ServiceGroup""" group = ServiceGroup('GROUP') ser1 = Service('I1') ser2 = Service('I2') eser1 = Service('E1') eser2 = Service('E2') eser3 = Service('E3') group.add_inter_dep(target=ser1) group.add_inter_dep(target=ser2) group.add_dep(target=eser1, parent=False) group.add_dep(target=eser2) group.add_dep(target=eser3) self.assertTrue(group.search('I1')) self.assertTrue(group.search('E2')) self.assertTrue(eser1.search('I1')) self.assertTrue(eser1.search('E3')) self.assertFalse(group.search('E0'))
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)