Esempio n. 1
0
    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)
Esempio n. 2
0
 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()
Esempio n. 3
0
    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)
Esempio n. 4
0
    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)
Esempio n. 5
0
 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()
Esempio n. 6
0
    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)
Esempio n. 7
0
    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)
Esempio n. 8
0
 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)
Esempio n. 9
0
 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)
Esempio n. 10
0
    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)
Esempio n. 11
0
    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)
Esempio n. 12
0
    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)
Esempio n. 13
0
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()
Esempio n. 14
0
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()
Esempio n. 15
0
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)
Esempio n. 16
0
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()