def test_multiprocess(self):
        """
        Test multiprocess get right all elements

        :return: None
        """
        print('')
        print('test creation')

        print('Create client API for URL:', self.backend_address)
        backend = Backend(self.backend_address, 8)
        backend.login('admin', 'admin')

        items = backend.get('realm')
        realm_id = items['_items'][0]['_id']

        # add 2000 commands
        backend.delete("command", {})
        data = {'command_line': 'check_ping', '_realm': realm_id}
        for i in range(1, 2001):
            data['name'] = "cmd %d" % i
            backend.post('command', data)

        # get without multiprocess
        backend_yannsolo = Backend(self.backend_address)
        backend_yannsolo.login('admin', 'admin')
        start_time = time.time()
        resp = backend_yannsolo.get_all('command', {'max_results': 20})
        threads_1 = time.time() - start_time
        self.assertEqual(len(resp['_items']), 2002,
                         "Number of commands in non multiprocess mode")

        # get with multiprocess (8 processes)
        start_time = time.time()
        resp = backend.get_all('command', {'max_results': 20})
        threads_8 = time.time() - start_time
        self.assertEqual(len(resp['_items']), 2002,
                         "Number of commands in multiprocess mode")
        ids = []
        for dat in resp['_items']:
            ids.append(dat['_id'])
        self.assertEqual(len(ids), 2002, "Number of id")
        # remove duplicates
        ids_final = set(ids)
        self.assertEqual(len(ids_final), 2002, "Number of id unique")

        print(threads_1)
        print(threads_8)
    def test_multiprocess(self):
        """
        Test multiprocess get right all elements

        :return: None
        """
        print('')
        print('test creation')

        print('Create client API for URL:', self.backend_address)
        backend = Backend(self.backend_address, 8)
        backend.login('admin', 'admin')

        items = backend.get('realm')
        realm_id = items['_items'][0]['_id']

        # add 700 commands
        backend.delete("command", {})
        data = {'command_line': 'check_ping', '_realm': realm_id}
        for i in range(1, 2001):
            data['name'] = "cmd %d" % i
            backend.post('command', data)

        # get without multiprocess
        backend_yannsolo = Backend(self.backend_address)
        backend_yannsolo.login('admin', 'admin')
        start_time = time.time()
        resp = backend_yannsolo.get_all('command', {'max_results': 20})
        threads_1 = time.time() - start_time
        self.assertEqual(len(resp['_items']), 2000, "Number of commands in non multiprocess mode")

        # get with multiprocess (8 processes)
        start_time = time.time()
        resp = backend.get_all('command', {'max_results': 20})
        threads_8 = time.time() - start_time
        self.assertEqual(len(resp['_items']), 2000, "Number of commands in multiprocess mode")
        ids = []
        for dat in resp['_items']:
            ids.append(dat['_id'])
        self.assertEqual(len(ids), 2000, "Number of id")
        # remove doubles
        ids_final = set(ids)
        self.assertEqual(len(ids_final), 2000, "Number of id unique")

        print(threads_1)
        print(threads_8)
示例#3
0
    def test_4_connection_error(self):
        """
        Backend connection error when getting an object...

        :return: None
        """
        print('test connection error when getting an object')

        # Create client API
        backend = Backend(self.backend_address)
        backend.login('admin', 'admin')

        print("stop the alignak backend")
        self.pid.kill()

        with assert_raises(BackendException) as cm:
            print('get all hostgroups at once')
            params = {'max_results': 3}
            backend.get_all('hostgroup', params=params)
        ex = cm.exception
        self.assertEqual(ex.code, 1000)
    def test_2_all_pages(self):
        """
        Get all items (so all pages) of a resource

        :return: None
        """
        print('')
        print('get all elements on an endpoint')

        # Create client API
        backend = Backend(self.backend_address)
        backend.login('admin', 'admin')

        # Get all elements
        print('get all hostgroups at once')
        params = {'max_results': 3}
        items = backend.get_all('hostgroup', params=params)
        hosts = items['_items']
        self.assertEqual(len(hosts), 101)
示例#5
0
    def test_2_all_pages(self):
        """
        Get all items (so all pages) of a resource

        :return: None
        """
        print('get all elements on an endpoint')

        # Create client API
        backend = Backend(self.backend_address)
        backend.login('admin', 'admin')

        # Get all elements
        print('get all hostgroups at once')
        params = {'max_results': 3}
        items = backend.get_all('hostgroup', params=params)
        hostgroups = items['_items']
        for hostgroup in hostgroups:
            print("Group: %s" % hostgroup['name'])
        self.assertEqual(len(hostgroups), 101)
class AlignakBackendArbiter(BaseModule):
    # pylint: disable=too-many-public-methods
    """ This class is used to get configuration from alignak-backend
    """

    def __init__(self, mod_conf):
        """
        Module initialization

        mod_conf is a dictionary that contains:
        - all the variables declared in the module configuration file
        - a 'properties' value that is the module properties as defined globally in this file

        :param mod_conf: module configuration file as a dictionary
        """
        BaseModule.__init__(self, mod_conf)

        # pylint: disable=global-statement
        global logger
        logger = logging.getLogger('alignak.module.%s' % self.alias)

        logger.debug("inner properties: %s", self.__dict__)
        logger.debug("received configuration: %s", mod_conf.__dict__)

        self.my_arbiter = None

        # Alignak backend importation script is running
        self.backend_import = False
        if 'ALIGNAK_BACKEND_IMPORT_RUN' in os.environ and os.environ['ALIGNAK_BACKEND_IMPORT_RUN']:
            logger.info("Alignak backend importation script is active.")
            self.backend_import = True

        self.url = getattr(mod_conf, 'api_url', 'http://localhost:5000')
        self.backend = Backend(self.url)
        self.backend.token = getattr(mod_conf, 'token', '')
        self.backend_connected = False
        if self.backend.token == '':
            self.getToken(getattr(mod_conf, 'username', ''), getattr(mod_conf, 'password', ''),
                          getattr(mod_conf, 'allowgeneratetoken', False))
        self.bypass_verify_mode = int(getattr(mod_conf, 'bypass_verify_mode', 0)) == 1
        logger.info(
            "bypass objects loading when Arbiter is in verify mode: %s",
            self.bypass_verify_mode
        )
        self.verify_modification = int(getattr(mod_conf, 'verify_modification', 5))
        logger.info(
            "configuration reload check period: %s minutes",
            self.verify_modification
        )
        self.action_check = int(getattr(mod_conf, 'action_check', 15))
        logger.info(
            "actions check period: %s seconds", self.action_check
        )
        self.next_check = 0
        self.next_action_check = 0
        self.time_loaded_conf = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")
        self.configraw = {}
        self.config = {'commands': [],
                       'timeperiods': [],
                       'hosts': [],
                       'hostgroups': [],
                       'services': [],
                       'contacts': [],
                       'contactgroups': [],
                       'servicegroups': [],
                       'realms': [],
                       'hostdependencies': [],
                       'hostescalations': [],
                       'servicedependencies': [],
                       'serviceescalations': [],
                       'triggers': []}

    # Common functions
    def do_loop_turn(self):
        """This function is called/used when you need a module with
        a loop function (and use the parameter 'external': True)
        """
        logger.info("In loop")
        time.sleep(1)

    def hook_read_configuration(self, arbiter):
        """
        Hook in arbiter used on configuration parsing start. This is useful to get our arbiter
        object and its parameters.

        :param arbiter: alignak.daemons.arbiterdaemon.Arbiter
        :type arbiter: object
        :return: None
        """
        self.my_arbiter = arbiter

    def getToken(self, username, password, generatetoken):
        """
        Authenticate and get the token

        :param username: login name
        :type username: str
        :param password: password
        :type password: str
        :param generatetoken: if True allow generate token, otherwise not generate
        :type generatetoken: bool
        :return: None
        """
        if self.backend_import:
            # Do no try to login when importing a configuration into the backend
            logger.info("Alignak backend importation script is active. "
                        "No backend connection.")
            return

        generate = 'enabled'
        if not generatetoken:
            generate = 'disabled'

        try:
            self.backend.login(username, password, generate)
            self.backend_connected = True
        except BackendException as exp:
            logger.warning("Alignak backend is not available for login. "
                           "No backend connection.")
            logger.exception("Exception: %s", exp)
            self.backend_connected = False

    def single_relation(self, resource, mapping, ctype):
        """
        Convert single embedded data to name of relation_data
        Example:
        {'contacts': {'_id': a3659204fe,'name':'admin'}}
        converted to:
        {'contacts': 'admin'}

        :param resource: dictionary got from alignak-backend
        :type resource: dict
        :param mapping: key value of resource
        :type mapping: str
        :param ctype: type of configraw (hosts, services, commands...)
        :type ctype: str
        """
        if mapping in resource:
            if resource[mapping] is not None:
                if resource[mapping] in self.configraw[ctype]:
                    resource[mapping] = self.configraw[ctype][resource[mapping]]

    def multiple_relation(self, resource, mapping, ctype):
        """
        Convert multiple embedded data to name of relation_data
        Example:
        {'contacts': [{'_id': a3659204fe,'contact_name':'admin'},
                      {'_id': a3659204ff,'contact_name':'admin2'}]}
        converted to:
        {'contacts': 'admin,admin2'}

        :param resource: dictionary got from alignak-backend
        :type resource: dict
        :param mapping: key value of resource
        :type mapping: str
        :param ctype: type of configraw (hosts, services, commands...)
        :type ctype: str
        """
        if mapping in resource:
            members = []
            for member in resource[mapping]:
                if member in self.configraw[ctype]:
                    members.append(self.configraw[ctype][member])
            resource[mapping] = ','.join(members)

    @classmethod
    def clean_unusable_keys(cls, resource):
        """
        Delete keys of dictionary not used

        :param resource: dictionary got from alignak-backend
        :type resource: dict
        :return:
        """
        fields = [
            '_links', '_updated', '_created', '_etag', '_id', 'name', 'ui', '_realm',
            '_sub_realm', '_users_read', '_users_update', '_users_delete', '_parent',
            '_tree_parents', '_all_children', '_level', 'customs', 'host', 'service',
            'back_role_super_admin', 'token', '_templates', '_template_fields', 'note',
            '_is_template', '_templates_with_services', '_templates_from_host_template',
            'merge_host_users', 'hosts_critical_threshold', 'hosts_warning_threshold',
            'services_critical_threshold', 'services_warning_threshold',
            'global_critical_threshold', 'global_warning_threshold', '_children',
            'hostgroups', 'hosts', 'dependent_hostgroups', 'dependent_hosts',
            'servicegroups', 'services', 'dependent_servicegroups', 'dependent_services',
            'usergroups', 'users',
            'location',
            'duplicate_foreach', 'tags',
            'ls_acknowledged', 'ls_current_attempt', 'ls_downtimed', 'ls_execution_time',
            'ls_grafana', 'ls_grafana_panelid', 'ls_impact', 'ls_last_check', 'ls_last_state',
            'ls_last_state_changed', 'ls_last_state_type', 'ls_latency', 'ls_long_output',
            'ls_max_attempts', 'ls_next_check', 'ls_output', 'ls_perf_data',
            'ls_state', 'ls_state_id', 'ls_state_type'
        ]
        for field in fields:
            if field in resource:
                del resource[field]

    @classmethod
    def convert_lists(cls, resource):
        """
        Convert lists into string with values separated with comma

        :param resource: ressource
        :type resource: dict
        :return: None
        """
        for prop in resource:
            if isinstance(resource[prop], list):
                resource[prop] = u','.join(str(e) for e in resource[prop])
            # Is it really useful ... considered as not useful!
            # elif isinstance(resource[prop], dict):
            # logger.warning("=====> %s", prop)
            # logger.warning(resource[prop])

    def get_realms(self):
        """
        Get realms from alignak_backend

        :return: None
        """
        self.configraw['realms'] = {}
        all_realms = self.backend.get_all('realm')
        logger.info("Got %d realms",
                    len(all_realms['_items']))
        for realm in all_realms['_items']:
            logger.info("- %s", realm['name'])
            self.configraw['realms'][realm['_id']] = realm['name']
            realm['imported_from'] = u'alignakbackend'
            realm['realm_name'] = realm['name']
            realm['realm_members'] = []
            self.clean_unusable_keys(realm)
            del realm['notes']
            del realm['alias']
            # self.convert_lists(realm)

            logger.debug("- realm: %s", realm)
            self.config['realms'].append(realm)

    def get_commands(self):
        """
        Get commands from alignak_backend

        :return: None
        """
        self.configraw['commands'] = {}
        all_commands = self.backend.get_all('command')
        logger.info("Got %d commands",
                    len(all_commands['_items']))
        for command in all_commands['_items']:
            logger.info("- %s", command['name'])
            self.configraw['commands'][command['_id']] = command['name']
            command['imported_from'] = u'alignakbackend'
            command['command_name'] = command['name']
            self.clean_unusable_keys(command)
            del command['alias']
            del command['notes']
            self.convert_lists(command)

            logger.debug("- command: %s", command)
            self.config['commands'].append(command)

    def get_timeperiods(self):
        """
        Get timeperiods from alignak_backend

        :return: None
        """
        self.configraw['timeperiods'] = {}
        all_timeperiods = self.backend.get_all('timeperiod')
        logger.info("Got %d timeperiods",
                    len(all_timeperiods['_items']))
        for timeperiod in all_timeperiods['_items']:
            logger.info("- %s", timeperiod['name'])
            self.configraw['timeperiods'][timeperiod['_id']] = timeperiod['name']
            timeperiod['imported_from'] = u'alignakbackend'
            timeperiod['timeperiod_name'] = timeperiod['name']
            for daterange in timeperiod['dateranges']:
                timeperiod.update(daterange)
            del timeperiod['dateranges']
            self.clean_unusable_keys(timeperiod)
            del timeperiod['notes']
            self.convert_lists(timeperiod)

            logger.debug("- timeperiod: %s", timeperiod)
            self.config['timeperiods'].append(timeperiod)

    def get_contactgroups(self):
        """
        Get contactgroups from alignak_backend

        :return: None
        """
        self.configraw['contactgroups'] = {}
        all_contactgroups = self.backend.get_all('usergroup')
        logger.info("Got %d contactgroups",
                    len(all_contactgroups['_items']))
        for contactgroup in all_contactgroups['_items']:
            logger.info("- %s", contactgroup['name'])
            self.configraw['contactgroups'][contactgroup['_id']] = contactgroup['name']

        for contactgroup in all_contactgroups['_items']:
            contactgroup[u'imported_from'] = u'alignakbackend'
            contactgroup[u'contactgroup_name'] = contactgroup['name']
            contactgroup[u'contactgroup_members'] = contactgroup['usergroups']
            contactgroup[u'members'] = contactgroup['users']
            # members
            self.multiple_relation(contactgroup, 'members', 'contacts')
            # contactgroup_members
            self.multiple_relation(contactgroup, 'contactgroup_members', 'contactgroups')
            self.clean_unusable_keys(contactgroup)
            del contactgroup['notes']
            self.convert_lists(contactgroup)

            logger.debug("- contacts group: %s", contactgroup)
            self.config['contactgroups'].append(contactgroup)

    def get_contacts(self):
        """
        Get contacts from alignak_backend

        :return: None
        """
        self.configraw['contacts'] = {}
        all_contacts = self.backend.get_all('user')
        logger.info("Got %d contacts",
                    len(all_contacts['_items']))
        for contact in all_contacts['_items']:
            logger.info("- %s", contact['name'])
            self.configraw['contacts'][contact['_id']] = contact['name']
            contact['imported_from'] = u'alignakbackend'
            contact['contact_name'] = contact['name']

            # host_notification_period
            self.single_relation(contact, 'host_notification_period', 'timeperiods')
            # service_notification_period
            self.single_relation(contact, 'service_notification_period', 'timeperiods')
            # host_notification_commands
            self.multiple_relation(contact, 'host_notification_commands', 'commands')
            # service_notification_commands
            self.multiple_relation(contact, 'service_notification_commands', 'commands')
            # contactgroups
            self.multiple_relation(contact, 'contactgroups', 'contactgroups')

            if 'host_notification_commands' not in contact:
                contact['host_notification_commands'] = ''
            if 'service_notification_commands' not in contact:
                contact['service_notification_commands'] = ''
            if 'host_notification_period' not in contact:
                contact['host_notification_period'] = \
                    self.config['timeperiods'][0]['timeperiod_name']
                contact['host_notifications_enabled'] = False
            if 'service_notification_period' not in contact:
                contact['service_notification_period'] = \
                    self.config['timeperiods'][0]['timeperiod_name']
                contact['service_notifications_enabled'] = False
            for key, value in contact['customs'].iteritems():
                contact[key] = value
            self.clean_unusable_keys(contact)
            del contact['notes']
            del contact['ui_preferences']
            self.convert_lists(contact)

            logger.debug("- contact: %s", contact)
            self.config['contacts'].append(contact)

    def get_hostgroups(self):
        """
        Get hostgroups from alignak_backend

        :return: None
        """
        self.configraw['hostgroups'] = {}
        all_hostgroups = self.backend.get_all('hostgroup')
        logger.info("Got %d hostgroups",
                    len(all_hostgroups['_items']))
        for hostgroup in all_hostgroups['_items']:
            logger.info("- %s", hostgroup['name'])
            self.configraw['hostgroups'][hostgroup['_id']] = hostgroup['name']

        for hostgroup in all_hostgroups['_items']:
            self.configraw['hostgroups'][hostgroup['_id']] = hostgroup['name']
            hostgroup[u'imported_from'] = u'alignakbackend'
            hostgroup[u'hostgroup_name'] = hostgroup['name']
            hostgroup[u'hostgroup_members'] = hostgroup['hostgroups']
            hostgroup[u'members'] = hostgroup['hosts']
            # members
            self.multiple_relation(hostgroup, 'members', 'hosts')
            # hostgroup_members
            self.multiple_relation(hostgroup, 'hostgroup_members', 'hostgroups')
            self.clean_unusable_keys(hostgroup)
            self.convert_lists(hostgroup)

            logger.debug("- hosts group: %s", hostgroup)
            self.config['hostgroups'].append(hostgroup)

    def get_hosts(self):
        """
        Get hosts from alignak_backend

        :return: None
        """
        self.configraw['hosts'] = {}
        all_hosts = self.backend.get_all('host', {"where": '{"_is_template": false}'})
        logger.info("Got %d hosts",
                    len(all_hosts['_items']))
        for host in all_hosts['_items']:
            logger.info("- %s", host['name'])
            self.configraw['hosts'][host['_id']] = host['name']
            host[u'host_name'] = host['name']
            host[u'imported_from'] = u'alignakbackend'
            # check_command
            if 'check_command' in host:
                if host['check_command'] is None:
                    host['check_command'] = ''
                elif host['check_command'] in self.configraw['commands']:
                    host['check_command'] = self.configraw['commands'][host['check_command']]
                else:
                    host['check_command'] = ''
            if 'check_command_args' in host:
                if 'check_command' not in host:
                    host['check_command'] = ''
                elif host['check_command_args'] != '':
                    host['check_command'] += '!'
                    host['check_command'] += host['check_command_args']
                del host['check_command_args']
            host[u'contacts'] = []
            if 'users' in host:
                host[u'contacts'] = host['users']
            host[u'contact_groups'] = []
            if 'usergroups' in host:
                host[u'contact_groups'] = host['usergroups']
            # check_period
            self.single_relation(host, 'check_period', 'timeperiods')
            # realm
            self.single_relation(host, '_realm', 'realms')
            host['realm'] = host['_realm']
            # notification_period
            self.single_relation(host, 'notification_period', 'timeperiods')
            # maintenance_period
            self.single_relation(host, 'maintenance_period', 'timeperiods')
            # snapshot_period
            self.single_relation(host, 'snapshot_period', 'timeperiods')
            # event_handler
            self.single_relation(host, 'event_handler', 'commands')
            # parents
            # ## self.multiple_relation(host, 'parents', 'host_name')
            host[u'parents'] = ''
            # hostgroups
            self.multiple_relation(host, 'hostgroup_name', 'hostgroups')
            # contacts
            self.multiple_relation(host, 'contacts', 'contacts')
            # contact_groups
            self.multiple_relation(host, 'contact_groups', 'contactgroups')
            # escalations
            # ## self.multiple_relation(host, 'escalations', 'escalation_name')
            del host['escalations']
            if 'alias' in host and host['alias'] == '':
                del host['alias']
            if 'realm' in host:
                if host['realm'] is None:
                    del host['realm']
            for key, value in host['customs'].iteritems():
                host[key] = value
            # Fix #9: inconsistent state when no retention module exists
            if 'ls_last_state' in host:
                if host['ls_state'] == 'UNREACHABLE':
                    host['initial_state'] = 'u'
                if host['ls_state'] == 'DOWN':
                    host['initial_state'] = 'd'
                if host['ls_state'] == 'UP':
                    host['initial_state'] = 'o'

                logger.debug(
                    "- host current live state is %s, "
                    "set initial_state as '%s'", host['ls_state'], host['initial_state']
                )
            self.clean_unusable_keys(host)
            self.convert_lists(host)

            logger.debug("- host: %s", host)
            self.config['hosts'].append(host)

    def get_servicegroups(self):
        """
        Get servicegroups from alignak_backend

        :return: None
        """
        self.configraw['servicegroups'] = {}
        all_servicegroups = self.backend.get_all('servicegroup')
        logger.info("Got %d servicegroups",
                    len(all_servicegroups['_items']))
        for servicegroup in all_servicegroups['_items']:
            logger.info("- %s", servicegroup['name'])
            self.configraw['servicegroups'][servicegroup['_id']] = servicegroup['name']

        for servicegroup in all_servicegroups['_items']:
            self.configraw['servicegroups'][servicegroup['_id']] = servicegroup['name']
            servicegroup['imported_from'] = u'alignakbackend'
            servicegroup['servicegroup_name'] = servicegroup['name']
            servicegroup[u'servicegroup_members'] = servicegroup['servicegroups']
            # members
            members = []
            for service in servicegroup['services']:
                if service not in self.configraw['services']:
                    continue
                for svc in self.config['services']:
                    if self.configraw['services'][service] == svc['service_description']:
                        members.append("%s,%s" % (svc['host_name'], svc['service_description']))
            servicegroup['members'] = ','.join(members)
            # servicegroup_members
            self.multiple_relation(servicegroup, 'servicegroup_members', 'servicegroups')
            self.clean_unusable_keys(servicegroup)
            self.convert_lists(servicegroup)

            logger.debug("- services group: %s", servicegroup)
            self.config['servicegroups'].append(servicegroup)

    def get_services(self):
        """
        Get services from alignak_backend

        :return: None
        """
        self.configraw['services'] = {}
        params = {'embedded': '{"escalations":1,"service_dependencies":1}',
                  "where": '{"_is_template": false}'}
        all_services = self.backend.get_all('service', params)
        logger.info("Got %d services",
                    len(all_services['_items']))
        for service in all_services['_items']:
            logger.info("- %s", service['name'])
            self.configraw['services'][service['_id']] = service['name']
            service['imported_from'] = u'alignakbackend'
            service['service_description'] = service['name']
            service['host_name'] = service['host']
            service['merge_host_contacts'] = service['merge_host_users']
            service['hostgroup_name'] = service['hostgroups']
            service[u'contacts'] = []
            if 'users' in service:
                service[u'contacts'] = service['users']
            service[u'contact_groups'] = []
            if 'usergroups' in service:
                service[u'contact_groups'] = service['usergroups']
            # check_command
            if 'check_command' in service:
                if service['check_command'] is None:
                    del service['check_command']
                elif service['check_command'] in self.configraw['commands']:
                    service['check_command'] = self.configraw['commands'][service['check_command']]
                else:
                    del service['check_command']
            if 'check_command_args' in service:
                if 'check_command' not in service:
                    service['check_command'] = ''
                else:
                    service['check_command'] += '!'
                service['check_command'] += service['check_command_args']
                del service['check_command_args']
            # host_name
            self.single_relation(service, 'host_name', 'hosts')
            # check_period
            self.single_relation(service, 'check_period', 'timeperiods')
            # notification_period
            self.single_relation(service, 'notification_period', 'timeperiods')
            # maintenance_period
            self.single_relation(service, 'maintenance_period', 'timeperiods')
            # snapshot_period
            self.single_relation(service, 'snapshot_period', 'timeperiods')
            # event_handler
            self.single_relation(service, 'event_handler', 'commands')
            # servicegroups
            self.multiple_relation(service, 'servicegroups', 'servicegroups')
            # contacts
            self.multiple_relation(service, 'contacts', 'contacts')
            # contact_groups
            self.multiple_relation(service, 'contact_groups', 'contactgroups')
            # escalations
            # ## self.multiple_relation(service, 'escalations', 'escalation_name')
            if 'escalation' in service and service['escalation'] == '':
                del service['escalation']
            # service_dependencies
            # ## self.multiple_relation(service, 'service_dependencies', 'service_name')
            service['service_dependencies'] = ''
            if 'alias' in service and service['alias'] == '':
                del service['alias']
            for key, value in service['customs'].iteritems():
                service[key] = value
            # Fix #9: inconsistent state when no retention module exists
            if 'ls_last_state' in service:
                if service['ls_state'] == 'UNKNOWN':
                    service['initial_state'] = 'u'
                if service['ls_state'] == 'CRITICAL':
                    service['initial_state'] = 'c'
                if service['ls_state'] == 'WARNING':
                    service['initial_state'] = 'w'
                if service['ls_state'] == 'UP':
                    service['initial_state'] = 'o'

                logger.debug(
                    "- service current live state is %s, "
                    "set initial_state as '%s'", service['ls_state'], service['initial_state']
                )

            self.clean_unusable_keys(service)
            self.convert_lists(service)

            logger.debug("- service: %s", service)
            self.config['services'].append(service)

    def get_hostdependencies(self):
        """
        Get hostdependencies from alignak_backend

        :return: None
        """
        self.configraw['hostdependencies'] = {}
        all_hostdependencies = self.backend.get_all('hostdependency')
        logger.info("Got %d hostdependencies",
                    len(all_hostdependencies['_items']))
        for hostdependency in all_hostdependencies['_items']:
            logger.info("- %s", hostdependency['name'])
            self.configraw['hostdependencies'][hostdependency['_id']] = hostdependency['name']
            hostdependency['imported_from'] = u'alignakbackend'
            # Do not exist in Alignak
            # hostdependency['hostdependency_name'] = hostdependency['name']

            hostdependency['dependent_hostgroup_name'] = hostdependency['dependent_hostgroups']
            hostdependency['dependent_host_name'] = hostdependency['dependent_hosts']
            hostdependency['hostgroup_name'] = hostdependency['hostgroups']
            hostdependency['host_name'] = hostdependency['hosts']

            # dependent_host_name
            self.multiple_relation(hostdependency, 'dependent_host_name', 'hosts')
            # dependent_hostgroup_name
            self.multiple_relation(hostdependency, 'dependent_hostgroup_name', 'hostgroups')
            # host_name
            self.multiple_relation(hostdependency, 'host_name', 'hosts')
            # hostgroup_name
            self.multiple_relation(hostdependency, 'hostgroup_name', 'hostgroups')
            self.clean_unusable_keys(hostdependency)
            self.convert_lists(hostdependency)

            logger.debug("- hosts dependency: %s", hostdependency)
            self.config['hostdependencies'].append(hostdependency)

    def get_hostescalations(self):
        """
        Get hostescalations from alignak_backend

        :return: None
        """
        self.configraw['hostescalations'] = {}
        all_hostescalations = self.backend.get_all('hostescalation')
        logger.info("Got %d hostescalations",
                    len(all_hostescalations['_items']))
        for hostescalation in all_hostescalations['_items']:
            logger.info("- %s", hostescalation['name'])
            self.configraw['hostescalations'][hostescalation['_id']] = hostescalation['name']
            hostescalation['hostescalation_name'] = hostescalation['name']
            hostescalation['imported_from'] = u'alignakbackend'
            # host_name
            self.single_relation(hostescalation, 'host_name', 'hosts')
            # hostgroup_name
            self.multiple_relation(hostescalation, 'hostgroup_name', 'hostgroups')
            # contacts
            self.multiple_relation(hostescalation, 'contacts', 'contacts')
            # contact_groups
            self.multiple_relation(hostescalation, 'contact_groups', 'contactgroups')
            self.clean_unusable_keys(hostescalation)
            self.convert_lists(hostescalation)

            logger.debug("- host escalation: %s", hostescalation)
            self.config['hostescalations'].append(hostescalation)

    def get_servicedependencies(self):
        """
        Get servicedependencies from alignak_backend

        :return: None
        """
        self.configraw['servicedependencies'] = {}
        all_servicedependencies = self.backend.get_all('servicedependency')
        logger.info("Got %d servicedependencies",
                    len(all_servicedependencies['_items']))
        for servicedependency in all_servicedependencies['_items']:
            logger.info("- %s", servicedependency['name'])
            self.configraw['servicedependencies'][servicedependency['_id']] = \
                servicedependency['name']
            servicedependency['imported_from'] = u'alignakbackend'
            # Do not exist in Alignak
            # servicedependency['servicedependency_name'] = servicedependency['name']

            servicedependency['dependent_hostgroup_name'] = \
                servicedependency['dependent_hostgroups']
            servicedependency['dependent_host_name'] = \
                servicedependency['dependent_hosts']
            servicedependency['dependent_service_description'] = \
                servicedependency['dependent_services']
            servicedependency['hostgroup_name'] = servicedependency['hostgroups']
            servicedependency['host_name'] = servicedependency['hosts']
            servicedependency['service_description'] = servicedependency['services']

            # dependent_host_name
            self.multiple_relation(servicedependency, 'dependent_host_name', 'hosts')
            # dependent_hostgroup_name
            self.multiple_relation(servicedependency, 'dependent_hostgroup_name', 'hostgroups')
            # service_description
            self.multiple_relation(servicedependency, 'service_description', 'services')
            # dependent_service_description
            self.multiple_relation(servicedependency, 'dependent_service_description', 'services')
            # host_name
            self.multiple_relation(servicedependency, 'host_name', 'hosts')
            # hostgroup_name
            self.multiple_relation(servicedependency, 'hostgroup_name', 'hostgroups')
            self.clean_unusable_keys(servicedependency)
            self.convert_lists(servicedependency)

            if not servicedependency['hostgroup_name']:
                del servicedependency['hostgroup_name']
            if not servicedependency['dependent_hostgroup_name']:
                del servicedependency['dependent_hostgroup_name']

            logger.debug("- services dependency: %s", servicedependency)
            self.config['servicedependencies'].append(servicedependency)

    def get_serviceescalations(self):
        """
        Get serviceescalations from alignak_backend

        :return: None
        """
        self.configraw['serviceescalations'] = {}
        all_serviceescalations = self.backend.get_all('serviceescalation')
        logger.info("Got %d serviceescalations",
                    len(all_serviceescalations['_items']))
        for serviceescalation in all_serviceescalations['_items']:
            logger.info("- %s", serviceescalation['name'])
            self.configraw['serviceescalations'][serviceescalation['_id']] = \
                serviceescalation['name']
            serviceescalation['serviceescalation_name'] = serviceescalation['name']
            serviceescalation['imported_from'] = u'alignakbackend'
            # host_name
            self.single_relation(serviceescalation, 'host_name', 'hosts')
            # hostgroup_name
            self.multiple_relation(serviceescalation, 'hostgroup_name', 'hostgroups')
            # service_description
            self.single_relation(serviceescalation, 'service_description', 'services')
            # contacts
            self.multiple_relation(serviceescalation, 'contacts', 'contacts')
            # contact_groups
            self.multiple_relation(serviceescalation, 'contact_groups', 'contactgroups')
            self.clean_unusable_keys(serviceescalation)
            self.convert_lists(serviceescalation)

            logger.debug("- service escalation: %s", serviceescalation)
            self.config['serviceescalations'].append(serviceescalation)

    def get_objects(self):
        """
        Get objects from alignak-backend

        :return: configuration objects
        :rtype: dict
        """
        if not self.backend_connected:
            logger.error("Alignak backend connection is not available. "
                         "Skipping objects load and provide an empty list to the Arbiter.")
            return self.config

        if self.my_arbiter and self.my_arbiter.verify_only:
            logger.info("my Arbiter is in verify only mode")
            if self.bypass_verify_mode:
                logger.info("configured to bypass the objects loading. "
                            "Skipping objects load and provide an empty list to the Arbiter.")
                return self.config

        if self.backend_import:
            logger.info("Alignak backend importation script is active. "
                        "Provide an empty objects list to the Arbiter.")
            return self.config

        start_time = time.time()
        try:
            self.get_realms()
            self.get_commands()
            self.get_timeperiods()
            self.get_contacts()
            self.get_contactgroups()
            self.get_hosts()
            self.get_hostgroups()
            self.get_services()
            self.get_servicegroups()
            self.get_hostdependencies()
            self.get_hostescalations()
            self.get_servicedependencies()
            self.get_serviceescalations()
        except BackendException as exp:
            logger.warning("Alignak backend is not available for reading. "
                           "Backend communication error.")
            logger.exception("Exception: %s", exp)
            self.backend_connected = False

        self.time_loaded_conf = datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")

        now = time.time()
        logger.info(
            "backend configuration loaded in %s seconds",
            (now - start_time)
        )

        # Schedule next configuration reload check in 10 minutes (need time to finish load config)
        self.next_check = int(now) + (60 * self.verify_modification)
        self.next_action_check = int(now) + self.action_check

        logger.info(
            "next configuration reload check in %s seconds ---",
            (self.next_check - int(now))
        )
        logger.info(
            "next actions check in %s seconds ---",
            (self.next_action_check - int(now))
        )
        return self.config

    def hook_tick(self, arbiter):
        """
        Hook in arbiter used to check if configuration has changed in the backend since
        last configuration loaded

        :param arbiter: alignak.daemons.arbiterdaemon.Arbiter
        :type arbiter: object
        :return: None
        """
        try:
            now = int(time.time())
            if now > self.next_check:
                logger.info(
                    "Check if system configuration changed in the backend..."
                )
                resources = [
                    'realm', 'command', 'timeperiod',
                    'usergroup', 'user',
                    'hostgroup', 'host', 'hostdependency', 'hostescalation',
                    'servicegroup', 'service', 'servicedependency', 'serviceescalation'
                ]
                reload_conf = False
                for resource in resources:
                    ret = self.backend.get(resource, {'where': '{"_updated":{"$gte": "' +
                                                               self.time_loaded_conf + '"}}'})
                    logger.info(
                        " - backend updated resource: %s, count: %d",
                        resource, ret['_meta']['total']
                    )
                    if ret['_meta']['total'] > 0:
                        reload_conf = True
                if reload_conf:
                    logger.warning(
                        "Hey, we must reload configuration from the backend!"
                    )
                    with open(arbiter.pidfile, 'r') as f:
                        arbiterpid = f.readline()
                    os.kill(int(arbiterpid), signal.SIGHUP)
                self.next_check = now + (60 * self.verify_modification)
                logger.debug(
                    "next configuration reload check in %s seconds ---",
                    (self.next_check - now)
                )

            if now > self.next_action_check:
                logger.debug("Check if acknowledgements are required...")
                self.get_acknowledge(arbiter)
                logger.debug("Check if downtime scheduling are required...")
                self.get_downtime(arbiter)
                logger.debug("Check if re-checks are required...")
                self.get_forcecheck(arbiter)

                self.next_action_check = now + self.action_check
                logger.debug(
                    "next actions check in %s seconds ---",
                    (self.next_action_check - int(now))
                )
        except Exception as exp:
            logger.warning("hook_tick exception: %s", str(exp))
            logger.exception("Exception: %s", exp)

    @staticmethod
    def convert_date_timestamp(mydate):
        """
        Convert date/time of backend into timestamp

        :param mydate: the date
        :type mydate: str
        :return: the timestamp
        :rtype: int
        """
        return int(time.mktime(datetime.strptime(mydate, "%a, %d %b %Y %H:%M:%S %Z").
                               timetuple()))

    def get_acknowledge(self, arbiter):
        """
        Get acknowledge from backend

        :return: None
        """
        all_ack = self.backend.get_all('actionacknowledge',
                                       {'where': '{"processed": false}',
                                        'embedded': '{"host": 1, "service": 1, "user": 1}'})
        for ack in all_ack['_items']:
            sticky = 1
            if ack['sticky']:
                sticky = 2
            if ack['action'] == 'add':
                if ack['service']:
                    command = '[{}] ACKNOWLEDGE_SVC_PROBLEM;{};{};{};{};{};{};{}\n'.\
                        format(self.convert_date_timestamp(ack['_created']), ack['host']['name'],
                               ack['service']['name'], sticky, int(ack['notify']),
                               int(ack['persistent']), ack['user']['name'], ack['comment'])
                else:
                    # logger.warning(time.time())
                    # logger.warning(self.convert_date_timestamp(ack['_created']))
                    command = '[{}] ACKNOWLEDGE_HOST_PROBLEM;{};{};{};{};{};{}\n'. \
                        format(self.convert_date_timestamp(ack['_created']), ack['host']['name'],
                               sticky, int(ack['notify']), int(ack['persistent']),
                               ack['user']['name'], ack['comment'])
            elif ack['action'] == 'delete':
                if ack['service']:
                    command = '[{}] REMOVE_SVC_ACKNOWLEDGEMENT;{};{}\n'.\
                        format(self.convert_date_timestamp(ack['_created']), ack['host']['name'],
                               ack['service']['name'])
                else:
                    command = '[{}] REMOVE_HOST_ACKNOWLEDGEMENT;{}\n'. \
                        format(self.convert_date_timestamp(ack['_created']), ack['host']['name'])

            headers = {'Content-Type': 'application/json', 'If-Match': ack['_etag']}
            data = {'processed': True}
            self.backend.patch('actionacknowledge/' + ack['_id'], data, headers)

            logger.info("build external command: %s", str(command))
            ext = ExternalCommand(command)
            arbiter.external_commands.append(ext)

    def get_downtime(self, arbiter):
        """
        Get downtime from backend

        :return: None
        """
        all_downt = self.backend.get_all('actiondowntime',
                                         {'where': '{"processed": false}',
                                          'embedded': '{"host": 1, "service": 1, '
                                                      '"user": 1}'})
        # pylint: disable=too-many-format-args
        for downt in all_downt['_items']:
            if downt['action'] == 'add':
                if downt['service']:
                    command = '[{}] SCHEDULE_SVC_DOWNTIME;{};{};{};{};{};{};{};{};{}\n'.\
                        format(self.convert_date_timestamp(downt['_created']),
                               downt['host']['name'], downt['service']['name'],
                               downt['start_time'], downt['end_time'], int(downt['fixed']),
                               0, downt['duration'], downt['user']['name'],
                               downt['comment'])
                elif downt['host'] and 'name' in downt['host']:
                    command = '[{}] SCHEDULE_HOST_DOWNTIME;{};{};{};{};{};{};{};{}\n'.\
                        format(self.convert_date_timestamp(downt['_created']),
                               downt['host']['name'], downt['start_time'], downt['end_time'],
                               int(downt['fixed']), 0, downt['duration'],
                               downt['user']['name'], downt['comment'])
            elif downt['action'] == 'delete':
                if downt['service']:
                    command = '[{}] DEL_ALL_SVC_DOWNTIMES;{};{}\n'.\
                        format(self.convert_date_timestamp(downt['_created']),
                               downt['host']['name'], downt['service']['name'])
                else:
                    command = '[{}] DEL_ALL_HOST_DOWNTIMES;{}\n'. \
                        format(self.convert_date_timestamp(downt['_created']),
                               downt['host']['name'])

            headers = {'Content-Type': 'application/json', 'If-Match': downt['_etag']}
            data = {'processed': True}
            self.backend.patch('actiondowntime/' + downt['_id'], data, headers)

            logger.info("build external command: %s", str(command))
            ext = ExternalCommand(command)
            arbiter.external_commands.append(ext)

    def get_forcecheck(self, arbiter):
        """
        Get forcecheck from backend

        :return: None
        """
        all_fcheck = self.backend.get_all('actionforcecheck',
                                          {'where': '{"processed": false}',
                                           'embedded': '{"host": 1, "service": 1}'})
        for fcheck in all_fcheck['_items']:
            timestamp = self.convert_date_timestamp(fcheck['_created'])
            if fcheck['service']:
                command = '[{}] SCHEDULE_FORCED_SVC_CHECK;{};{};{}\n'.\
                    format(timestamp, fcheck['host']['name'], fcheck['service']['name'], timestamp)
            else:
                command = '[{}] SCHEDULE_FORCED_HOST_CHECK;{};{}\n'.\
                    format(timestamp, fcheck['host']['name'], timestamp)

            headers = {'Content-Type': 'application/json', 'If-Match': fcheck['_etag']}
            data = {'processed': True}
            self.backend.patch('actionforcecheck/' + fcheck['_id'], data, headers)

            logger.info("build external command: %s", str(command))
            ext = ExternalCommand(command)
            arbiter.external_commands.append(ext)
示例#7
0
    class __BackendConnection(object):
        """
        Base class for all objects state management (displayed icon, ...)
        """

        def __init__(self, backend_endpoint='http://127.0.0.1:5002'):
            self.backend_endpoint = backend_endpoint
            self.backend = Backend(backend_endpoint)
            self.connected = False

        def login(self, username, password=None):
            """
            Log into the backend

            If password is provided, use the backend login function to authenticate the user

            If no password is provided, the username is assumed to be an authentication token and we
            use the backend connect function.
            """
            logger.info("login, connection requested, login: %s", username)

            self.connected = False

            if not username:  # pragma: no cover, should not happen
                # Refuse backend login without username
                logger.warning("No login without username!")
                return self.connected

            if not password:  # pragma: no cover, should not happen
                # Set backend token (no login request).
                logger.debug("Update backend token")
                self.backend.token = username
                self.connected = True
                return self.connected

            try:
                # Backend real login
                logger.info("Requesting backend authentication, username: %s", username)
                self.connected = self.backend.login(username, password)
            except BackendException:  # pragma: no cover, should not happen
                logger.warning("configured backend is not available!")
            except Exception as e:  # pragma: no cover, should not happen
                logger.warning("User login exception: %s", str(e))
                logger.error("traceback: %s", traceback.format_exc())

            logger.info("login result: %s", self.connected)
            return self.connected

        def logout(self):
            """
            Log out from the backend

            Do nothing except setting 'connected' attribute to False
            """
            logger.info("logout")

            self.connected = False

        def count(self, object_type, params=None):
            """
            If params is a string, it is considered to be an object id and params
            is modified to {'_id': params}.

            Else, params is used to 'get' objects from the backend.
            """
            logger.debug("count, %s, params: %s", object_type, params)

            if isinstance(params, basestring):
                params = {'where': {'_id': params}}

            # Update backend search parameters
            if params is None:
                params = {'page': 0, 'max_results': 1}
            if 'where' in params:
                params['where'] = json.dumps(params['where'])
            if 'max_results' not in params:
                params['max_results'] = 1
            logger.debug(
                "count, search in the backend for %s: parameters=%s", object_type, params
            )

            try:
                result = self.backend.get(object_type, params=params)
            except BackendException as e:  # pragma: no cover, simple protection
                logger.warning("count, backend exception for %s: %s", object_type, str(e))
                return 0

            logger.debug("count, search result for %s: result=%s", object_type, result)
            if not result['_status'] == 'OK':  # pragma: no cover, should not happen
                error = []
                if "content" in result:
                    error.append(result['content'])
                if "_issues" in result:
                    error.append(result['_issues'])
                logger.warning("count, %s: %s, not found: %s", object_type, params, error)
                return 0

            # If more than one element is found, we get an _items list
            if '_items' in result:
                logger.debug("count, found in the backend: %s: %s", object_type, result['_items'])
                return result['_meta']['total']

            return 0  # pragma: no cover, simple protection

        def get(self, object_type, params=None, all_elements=False):
            """
            If params is a string, it is considered to be an object id and params
            is modified to {'_id': params}.

            Else, params is used to 'get' objects from the backend.

            Returns an object or an array of matching objects. All extra attributes
            (_links, _status, _meta, ...) are not returned but an '_total' attribute is added
            in each element to get the total count of elements stored in the backend.

            Returns None if the search failed. Do not raise any exception to the caller.

            If all_elements is True, it calls the get_all function of the backend client to
            get all the elements without any pagination activated.
            """
            logger.debug("get, %s, params: %s", object_type, params)

            if isinstance(params, basestring):
                params = {'where': {'_id': params}}
                logger.debug("get, %s, params: %s", object_type, params)

            # Update backend search parameters
            if params is None:
                params = {'page': 0, 'max_results': BACKEND_PAGINATION_LIMIT}
            if 'where' in params:
                params['where'] = json.dumps(params['where'])
            if 'embedded' in params:
                params['embedded'] = json.dumps(params['embedded'])
            if 'where' not in params:
                params['where'] = {}
            if 'page' not in params:
                params['page'] = 0
            if 'max_results' not in params:
                params['max_results'] = BACKEND_PAGINATION_LIMIT
            logger.debug(
                "get, search in the backend for %s: parameters=%s", object_type, params
            )

            try:
                if all_elements:
                    result = self.backend.get_all(object_type, params=params)
                else:
                    result = self.backend.get(object_type, params=params)
            except BackendException as e:  # pragma: no cover, simple protection
                logger.warning("get, backend exception for %s: %s", object_type, str(e))
                return None

            logger.debug(
                "search, search result for %s: result=%s", object_type, result
            )
            if result['_status'] != 'OK':  # pragma: no cover, should not happen
                error = []
                if "content" in result:
                    error.append(result['content'])
                if "_issues" in result:
                    error.append(result['_issues'])
                logger.warning("get, %s: %s, not found: %s", object_type, params, error)
                raise ValueError(
                    '%s, search: %s was not found in the backend, error: %s' % (
                        object_type, params, error
                    )
                )

            # If more than one element is found, we get an _items list
            if '_items' in result:
                if '_meta' in result:
                    for item in result['_items']:
                        item.update({'_total': result['_meta']['total']})
                logger.debug("get, found in the backend: %s: %s", object_type, result['_items'])
                return result['_items']

            if '_status' in result:
                result.pop('_status')
            if '_meta' in result:
                # result.update({'_total': result['_meta']['total']})
                result['_total'] = result['_meta']['total']
            logger.debug("get, found one in the backend: %s: %s", object_type, result)
            return result

        def post(self, object_type, data=None, files=None):
            """ Add an element """
            logger.info("post, request to add a %s: data: %s", object_type, data)

            # Do not set header to use the client default behavior:
            # - set headers as {'Content-Type': 'application/json'}
            # - encode provided data to JSON
            headers = None
            if files:
                logger.info("post, request to add a %s with files: %s", object_type, files)
                # Set header to disable client default behavior
                headers = {'Content-type': 'multipart/form-data'}

            try:
                result = self.backend.post(object_type, data=data, files=files, headers=headers)
                logger.debug("post, response: %s", result)
                if result['_status'] != 'OK':
                    logger.warning("post, error: %s", result)
                    return None
            except BackendException as e:  # pragma: no cover, simple protection
                logger.error("post, backend exception: %s", str(e))
                logger.error("- response: %s", e.response)
                return None
            except Exception as e:  # pragma: no cover, simple protection
                logger.warning("post, error: %s", str(e))
                return None

            return result['_id']

        def delete(self, object_type, object_id):
            """
            Delete an element
            - object_type is the element type
            - object_id is the element identifier
            """
            logger.info("delete, request to delete the %s: %s", object_type, object_id)

            try:
                # Get most recent version of the element
                element = self.get('/'.join([object_type, object_id]))
                logger.debug("delete, element: %s", element)
            except ValueError:  # pragma: no cover, simple protection
                logger.warning("delete, object %s, _id=%s not found", object_type, object_id)
                return False

            try:
                # Request deletion
                headers = {'If-Match': element['_etag']}
                endpoint = '/'.join([object_type, object_id])
                logger.info("delete, endpoint: %s", endpoint)
                result = self.backend.delete(endpoint, headers)
                logger.debug("delete, response: %s", result)
                if result['_status'] != 'OK':  # pragma: no cover, should never happen
                    error = []
                    if "content" in result:
                        error.append(result["content"])
                    if "_issues" in result:
                        error.append(result["_issues"])
                        for issue in result["_issues"]:
                            error.append(result["_issues"][issue])
                    logger.warning("delete, error: %s", error)
                    return False
            except BackendException as e:  # pragma: no cover, should never happen
                logger.error("delete, backend exception: %s", str(e))
                return False
            except ValueError:  # pragma: no cover, should never happen
                logger.warning("delete, not found %s: %s", object_type, element)
                return False

            return True

        def update(self, object_type, object_id, data):
            """
            Update an element
            - object_type is the element type
            - object_id is the element identifier
            """
            logger.info("update, request to update the %s: %s", object_type, object_id)

            try:
                # Get most recent version of the element
                element = self.get('/'.join([object_type, object_id]))
                logger.debug("update, element: %s", element)
            except ValueError:  # pragma: no cover, simple protection
                logger.warning("update, object %s, _id=%s not found", object_type, object_id)
                return False

            try:
                # Request update
                headers = {'If-Match': element['_etag']}
                endpoint = '/'.join([object_type, object_id])
                logger.info("update, endpoint: %s, data: %s", endpoint, data)
                result = self.backend.patch(endpoint, data, headers)
                logger.debug("update, response: %s", result)
                if result['_status'] != 'OK':  # pragma: no cover, should never happen
                    error = []
                    if "content" in result:
                        error.append(result["content"])
                    if "_issues" in result:
                        error.append(result["_issues"])
                        for issue in result["_issues"]:
                            error.append(result["_issues"][issue])
                    logger.warning("update, error: %s", error)
                    return False
            except BackendException as e:  # pragma: no cover, should never happen
                logger.error("update, backend exception: %s", str(e))
                return False
            except ValueError:  # pragma: no cover, should never happen
                logger.warning("update, not found %s: %s", object_type, element)
                return False

            return True
    def test_22_post_patch_delete(self):
        global backend_address

        print ''
        print 'post/delete/patch some hostgroups'

        # Create client API
        backend = Backend(backend_address)

        print 'Login ...'
        print 'authenticated:', backend.authenticated
        result = backend.login('admin', 'admin')
        print 'authenticated:', backend.authenticated
        print 'token:', backend.token
        assert_true(backend.authenticated)

        # Get all hostgroups
        print 'get all hostgroups at once'
        items = backend.get_all('hostgroup')
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        for item in items:
            assert_true('hostgroup_name' in item)
            assert_true('_id' in item)
            assert_true('_etag' in item)
            print "Group: ", item['hostgroup_name'], item['_id']
            # Test contact still exists ... delete him!
            if item['hostgroup_name'] == 'test':
                headers = { 'If-Match': item['_etag'] }
                response = backend.delete('/'.join(['hostgroup', item['_id']]), headers)
                print "Response:", response

        # Create a new hostgroup, bad parameters
        print 'create a hostgroup, missing fields'
        # Mandatory field hostgroup_name is missing ...
        data = {
            "name": "Testing hostgroup",
            "alias": "Fred",
            "back_role_super_admin": False,
            "back_role_admin": [],
            "min_business_impact": 0,
        }
        with assert_raises(BackendException) as cm:
            response = backend.post('hostgroup', data=data)
        ex = cm.exception
        print 'exception:', str(ex.code), ex.message, ex.response
        if "_issues" in ex.response:
            for issue in ex.response["_issues"]:
                print "Issue: %s - %s" %(issue, ex.response["_issues"][issue])
        assert_true(ex.code == 422)
        assert_true(ex.response["_issues"])

        # Create a new hostgroup
        print 'create a hostgroup'
        data = {
            "hostgroup_name": "test",
            "name": "Testing hostgroup",
            "alias": "Fred",
            "note": "Hostgroup note ...",
            "realm": "all"
        }
        response = backend.post('hostgroup', data=data)
        print "Response:", response
        assert_true('_created' in response)
        assert_true('_updated' in response)
        assert_true(response['_created'] == response['_updated'])

        # Get all hostgroups
        print 'get all hostgroups at once'
        # Filter the templates ...
        items = backend.get_all('hostgroup')
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        assert_true(len(items) > 0)
        # Search test hostgroup
        hostgroup_id = ''
        hostgroup_etag = ''
        for item in items:
            assert_true('hostgroup_name' in item)
            print "hostgroup: ", item['hostgroup_name']
            if item['hostgroup_name'] == 'test':
                hostgroup_id = item['_id']
                hostgroup_etag = item['_etag']
        assert_true(hostgroup_id != '')
        assert_true(hostgroup_etag != '')

        print 'changing hostgroup alias ... no _etag'
        print 'id:', hostgroup_id
        print 'etag:', hostgroup_etag
        with assert_raises(BackendException) as cm:
            data = {'alias': 'modified with no header'}
            # headers['If-Match'] = hostgroup_etag
            response = backend.patch('/'.join(['hostgroup', hostgroup_id]), data=data)
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 1005, str(ex))

        print 'changing hostgroup alias ...'
        print 'id:', hostgroup_id
        print 'etag:', hostgroup_etag
        data = {'alias': 'modified test'}
        headers = {'If-Match': hostgroup_etag}
        response = backend.patch('/'.join(['hostgroup', hostgroup_id]), data=data, headers=headers)
        print 'response:', response
        assert_true(response['_status'] == 'OK')

        response = backend.get('/'.join(['hostgroup', hostgroup_id]))
        print 'response:', response
        assert_true(response['alias'] == 'modified test')

        print 'changing hostgroup alias ... bad _etag (inception = True)'
        print 'id:', hostgroup_id
        print 'etag:', hostgroup_etag
        data = {'alias': 'modified test again'}
        headers = {'If-Match': hostgroup_etag}
        response = backend.patch('/'.join(['hostgroup', hostgroup_id]), data=data, headers=headers, inception=True)
        print 'response:', response
        assert_true(response['_status'] == 'OK')

        response = backend.get('/'.join(['hostgroup', hostgroup_id]))
        print 'response:', response
        assert_true(response['alias'] == 'modified test again')

        print 'changing hostgroup alias ... bad _etag (inception = False)'
        print 'id:', hostgroup_id
        print 'etag:', hostgroup_etag
        with assert_raises(BackendException) as cm:
            data = {'alias': 'modified test again and again'}
            headers = {'If-Match': hostgroup_etag}
            response = backend.patch('/'.join(['hostgroup', hostgroup_id]), data=data, headers=headers)
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 412, str(ex))

        response = backend.get('/'.join(['hostgroup', hostgroup_id]))
        print 'response:', response
        # Not changed !
        assert_true(response['alias'] == 'modified test again')

        response = backend.get('/'.join(['hostgroup', hostgroup_id]))
        print 'response:', response
        # Not changed !
        assert_true(response['alias'] == 'modified test again')

        print 'deleting hostgroup ... bad href'
        with assert_raises(BackendException) as cm:
            headers = { 'If-Match': item['_etag'] }
            response = backend.delete('/'.join(['hostgroup', '5'+item['_id']]), headers)
            print "Response:", response
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 1003, str(ex))
    def test_04_login(self):
        """
        Test with right username / password

        :return: None
        """
        print('')
        print('test accepted connection with username/password')

        # Create client API
        backend = Backend(self.backend_address)

        print('Login ...')
        assert backend.login('admin', 'admin')
        print('authenticated:', backend.authenticated)
        print('token:', backend.token)
        assert_true(backend.authenticated)

        print('Logout ...')
        backend.logout()
        print('authenticated:', backend.authenticated)
        print('token:', backend.token)
        assert_false(backend.authenticated)

        print('Login ...')
        print('authenticated:', backend.authenticated)
        assert backend.login('admin', 'admin')
        print('authenticated:', backend.authenticated)
        print('token:', backend.token)
        assert_true(backend.authenticated)

        print('Logout ...')
        backend.logout()
        print('authenticated:', backend.authenticated)
        print('token:', backend.token)
        assert_false(backend.authenticated)

        print('Logout ...')
        backend.logout()
        print('authenticated:', backend.authenticated)
        print('token:', backend.token)
        assert_false(backend.authenticated)

        print('get object ... must be refused!')
        with assert_raises(BackendException) as cm:
            backend.get('host')
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 1001, str(ex))

        print('get_all object ... must be refused!')
        with assert_raises(BackendException) as cm:
            backend.get_all('host')
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 1001, str(ex))

        print('get all domains ... must be refused!')
        with assert_raises(BackendException) as cm:
            backend.get_domains()
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 1001, str(ex))

        print('post data ... must be refused!')
        with assert_raises(BackendException) as cm:
            data = {'fake': 'fake'}
            backend.post('user', data=data)
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 1001, str(ex))

        print('patch data ... must be refused!')
        with assert_raises(BackendException) as cm:
            data = {'fake': 'fake'}
            headers = {'If-Match': ''}
            backend.patch('user', data=data, headers=headers)
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 1001, str(ex))

        print('delete data ... must be refused!')
        with assert_raises(BackendException) as cm:
            headers = {'If-Match': ''}
            backend.delete('user', headers=headers)
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 1001, str(ex))
    def test_12_all_pages(self):
        global backend_address

        print ''
        print 'get all elements on an endpoint'

        # Create client API
        backend = Backend(backend_address)

        print 'Login ...'
        print 'authenticated:', backend.authenticated
        result = backend.login('admin', 'admin')
        print 'authenticated:', backend.authenticated
        print 'token:', backend.token
        assert_true(backend.authenticated)

        # Get all available endpoints
        print 'get all domains'
        items = backend.get_domains()
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        assert_true(len(items) > 0)
        for item in items:
            if item['href'] in ['loghost', 'logservice']:
                continue
            assert_true('href' in item)
            assert_true('title' in item)
            print "Domain: ", item

            # Get all elements
            print 'get all %s at once' % item['href']
            items = backend.get_all(item['href'])
            print "Got %d elements:" % len(items)
            assert_true('_items' not in items)
            # assert_true(len(items) > 0)
            for item in items:
                assert_true('_etag' in item)
                print "etag: ", item['_etag']

        # Get all available endpoints
        print 'get all domains'
        items = backend.get_domains()
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        assert_true(len(items) > 0)
        for item in items:
            if item['href'] in ['loghost', 'logservice']:
                continue
            assert_true('href' in item)
            assert_true('title' in item)
            print "Domain: ", item

            # Get all elements
            print 'get all %s at once' % item['href']
            params = {'max_results': 2}
            items = backend.get_all(item['href'], params=params)
            print "Got %d elements:" % len(items)
            assert_true('_items' not in items)
            # assert_true(len(items) > 0)
            for item in items:
                assert_true('_etag' in item)
                print "etag: ", item['_etag']

        # Get all hosts
        print 'get all hosts at once, 1 item per page'
        params = {'max_results':1}
        items = backend.get_all(item['href'], params=params)
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        # assert_true(len(items) > 0)
        for item in items:
            assert_true('_etag' in item)
            print "etag: ", item['_etag']
    def test_21_post_patch_delete(self):
        global backend_address

        print ''
        print 'post/delete/patch some elements'

        # Create client API
        backend = Backend(backend_address)

        print 'Login ...'
        print 'authenticated:', backend.authenticated
        result = backend.login('admin', 'admin')
        print 'authenticated:', backend.authenticated
        print 'token:', backend.token
        assert_true(backend.authenticated)

        # Get all contacts
        print 'get all contacts at once'
        parameters = { 'where': '{"register":true}' }
        items = backend.get_all('contact', params=parameters)
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        for item in items:
            assert_true('contact_name' in item)
            assert_true('_id' in item)
            assert_true('_etag' in item)
            print "Contact: ", item['contact_name'], item['_id']
            # Test contact still exists ... delete him!
            if item['contact_name'] == 'test':
                headers = { 'If-Match': item['_etag'] }
                response = backend.delete('/'.join(['contact', item['_id']]), headers)
                print "Response:", response

        # Get all timeperiods
        print 'get all timeperiods at once'
        parameters = { 'where': '{"register":true}' }
        items = backend.get_all('timeperiod', params=parameters)
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        tp_id = ''
        for item in items:
            assert_true('timeperiod_name' in item)
            assert_true('_id' in item)
            tp_id = item['_id']
            print item
            print "TP: %s (%s), id=%s" % (item['timeperiod_name'], item['name'], item['_id'])

        if not tp_id:
            # Create a new timeperiod
            print 'create a timeperiod'
            data = {
                "timeperiod_name": "test",
                "name": "Testing TP",
                "alias": "Test TP",
                "dateranges": [
                    {u'monday': u'09:00-17:00'},
                    {u'tuesday': u'09:00-17:00'},
                    {u'wednesday': u'09:00-17:00'},
                    {u'thursday': u'09:00-17:00'},
                    {u'friday': u'09:00-17:00'}
                ],
                "register": True
            }
            response = backend.post('timeperiod', data=data)
            print "Response:", response
            assert_true('_created' in response)
            assert_true('_updated' in response)
            assert_true(response['_created'] == response['_updated'])

            # Get all timeperiods
            print 'get all timeperiods at once'
            parameters = { 'where': '{"register":true}' }
            items = backend.get_all('timeperiod', params=parameters)
            print "Got %d elements:" % len(items)
            assert_true('_items' not in items)
            tp_id = ''
            for item in items:
                assert_true('timeperiod_name' in item)
                assert_true('_id' in item)
                tp_id = item['_id']
                print "TP: %s (%s), id=%s" % (item['timeperiod_name'], item['name'], item['_id'])

        assert_true(tp_id != '')

        # Create a new contact, bad parameters
        print 'create a contact, missing fields'
        # Mandatory field contact_name is missing ...
        data = {
            "name": "Testing contact",
            "alias": "Fred",
            "back_role_super_admin": False,
            "back_role_admin": [],
            "min_business_impact": 0,
        }
        with assert_raises(BackendException) as cm:
            response = backend.post('contact', data=data)
        ex = cm.exception
        print 'exception:', str(ex.code), ex.message, ex.response
        if "_issues" in ex.response:
            for issue in ex.response["_issues"]:
                print "Issue: %s - %s" %(issue, ex.response["_issues"][issue])
        assert_true(ex.code == 422)
        assert_true(ex.response["_issues"])

        # Create a new contact
        print 'create a contact'
        data = {
            "contact_name": "test",
            "name": "Testing contact",
            "alias": "Fred",
            "back_role_super_admin": False,
            "back_role_admin": [],
            "min_business_impact": 0,
            "email": "*****@*****.**",

            "is_admin": False,
            "expert": False,
            "can_submit_commands": False,

            "host_notifications_enabled": True,
            "host_notification_period": tp_id,
            "host_notification_commands": [
            ],
            "host_notification_options": [
                "d",
                "u",
                "r"
            ],

            "service_notifications_enabled": True,
            "service_notification_period": tp_id,
            "service_notification_commands": [ ],
            "service_notification_options": [
                "w",
                "u",
                "c",
                "r"
            ],
            "retain_status_information": False,
            "note": "Monitoring template : default",
            "retain_nonstatus_information": False,
            "definition_order": 100,
            "address1": "",
            "address2": "",
            "address3": "",
            "address4": "",
            "address5": "",
            "address6": "",
            "pager": "",
            "notificationways": [],
            "register": True
        }
        response = backend.post('contact', data=data)
        print "Response:", response
        assert_true('_created' in response)
        assert_true('_updated' in response)
        assert_true(response['_created'] == response['_updated'])

        # Get all contacts
        print 'get all contacts at once'
        # Filter the templates ...
        parameters = { 'where': '{"register":true}' }
        items = backend.get_all('contact', params=parameters)
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        assert_true(len(items) > 0)
        # Search test contact
        contact_id = ''
        contact_etag = ''
        for item in items:
            assert_true('contact_name' in item)
            print "Contact: ", item['contact_name']
            if item['contact_name'] == 'test':
                contact_id = item['_id']
                contact_etag = item['_etag']
        assert_true(contact_id != '')
        assert_true(contact_etag != '')

        print 'changing contact alias ... no _etag'
        print 'id:', contact_id
        print 'etag:', contact_etag
        with assert_raises(BackendException) as cm:
            data = {'alias': 'modified with no header'}
            # headers['If-Match'] = contact_etag
            response = backend.patch('/'.join(['contact', contact_id]), data=data)
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 1005, str(ex))

        print 'changing contact alias ...'
        print 'id:', contact_id
        print 'etag:', contact_etag
        data = {'alias': 'modified test'}
        headers = {'If-Match': contact_etag}
        response = backend.patch('/'.join(['contact', contact_id]), data=data, headers=headers)
        print 'response:', response
        assert_true(response['_status'] == 'OK')

        response = backend.get('/'.join(['contact', contact_id]))
        print 'response:', response
        assert_true(response['alias'] == 'modified test')

        print 'changing contact alias ... bad _etag (inception = True)'
        print 'id:', contact_id
        print 'etag:', contact_etag
        data = {'alias': 'modified test again'}
        headers = {'If-Match': contact_etag}
        response = backend.patch('/'.join(['contact', contact_id]), data=data, headers=headers, inception=True)
        print 'response:', response
        assert_true(response['_status'] == 'OK')

        response = backend.get('/'.join(['contact', contact_id]))
        print 'response:', response
        assert_true(response['alias'] == 'modified test again')

        print 'changing contact unknown field ... must be refused'
        print 'id:', contact_id
        print 'etag:', contact_etag
        with assert_raises(BackendException) as cm:
            data = {'bad_field': 'bad field name ... unknown in data model'}
            headers = {'If-Match': contact_etag}
            response = backend.patch('/'.join(['contact', contact_id]), data=data, headers=headers, inception=True)
        ex = cm.exception
        print 'exception:', str(ex.code), ex.message, ex.response
        if "_issues" in ex.response:
            for issue in ex.response["_issues"]:
                print "Issue: %s - %s" %(issue, ex.response["_issues"][issue])
        assert_true(ex.code == 422)
        assert_true(ex.response["_issues"])

        print 'changing contact alias ... bad _etag (inception = False)'
        print 'id:', contact_id
        print 'etag:', contact_etag
        with assert_raises(BackendException) as cm:
            data = {'alias': 'modified test again and again'}
            headers = {'If-Match': contact_etag}
            response = backend.patch('/'.join(['contact', contact_id]), data=data, headers=headers)
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 412, str(ex))

        response = backend.get('/'.join(['contact', contact_id]))
        print 'response:', response
        # Not changed !
        assert_true(response['alias'] == 'modified test again')

        response = backend.get('/'.join(['contact', contact_id]))
        print 'response:', response
        # Not changed !
        assert_true(response['alias'] == 'modified test again')

        print 'deleting contact ... bad href'
        with assert_raises(BackendException) as cm:
            headers = { 'If-Match': item['_etag'] }
            response = backend.delete('/'.join(['contact', '5'+item['_id']]), headers)
            print "Response:", response
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 1003, str(ex))
class BackendConnection(object):    # pylint: disable=too-few-public-methods

    """Base class for Alignak backend connection"""

    def __init__(self, backend_endpoint='http://127.0.0.1:5000'):
        self.backend_endpoint = backend_endpoint
        self.alignak_backend = Backend(backend_endpoint)
        self.connected = False

    def login(self, username, password=None):
        """Log into the backend

        If password is provided, use the backend login function to authenticate the user

        If no password is provided, the username is assumed to be an authentication token and we
        use the backend connect function."""

        logger.info("login, connection requested, login: %s", username)

        self.connected = False

        if not username:
            # Refuse backend login without username
            logger.warning("No login without username!")
            return self.connected

        if not password:
            # Set backend token (no login request).
            logger.debug("Update backend token")
            self.alignak_backend.token = username
            self.connected = True
            return self.connected

        try:
            # Backend real login
            logger.info("Requesting backend (%s) authentication, username: %s",
                        self.backend_endpoint, username)
            self.connected = self.alignak_backend.login(username, password)
        except BackendException:  # pragma: no cover, should not happen
            logger.warning("configured backend is not available!")
        except Exception as e:  # pragma: no cover, should not happen
            logger.warning("User login exception: %s", str(e))
            logger.error("traceback: %s", traceback.format_exc())

        logger.info("login result: %s", self.connected)
        return self.connected

    def logout(self):
        """Log out from the backend

        Do nothing except setting 'connected' attribute to False"""

        logger.info("logout")

        self.connected = False

    def count(self, object_type, params=None):
        """Count backend elements

        If params is a string, it is considered to be an object id and params
        is modified to {'_id': params}.

        Else, params is used to 'get' objects from the backend."""

        logger.debug("count, %s, params: %s", object_type, params)

        if isinstance(params, string_types):
            params = {'where': {'_id': params}}

        # Update backend search parameters
        if params is None:
            params = {'page': 0, 'max_results': BACKEND_PAGINATION_LIMIT}
        if 'where' in params:
            params['where'] = json.dumps(params['where'])
        if 'embedded' in params:
            del params['embedded']
        if 'where' not in params:
            params['where'] = {}
        if 'page' not in params:
            params['page'] = 0
        if 'max_results' not in params:
            params['max_results'] = BACKEND_PAGINATION_LIMIT
        # if params is None:
            # params = {'page': 0, 'max_results': 1}
        # if 'where' in params:
            # params['where'] = json.dumps(params['where'])
        # if 'max_results' not in params:
            # params['max_results'] = 1
        logger.debug("count, search in the backend for %s: parameters=%s", object_type, params)

        try:
            result = self.alignak_backend.get(object_type, params=params)
        except BackendException as e:  # pragma: no cover, simple protection
            logger.warning("count, backend exception for %s: %s", object_type, str(e))
            return 0

        if not result['_status'] == 'OK':  # pragma: no cover, should not happen
            error = []
            if "content" in result:
                error.append(result['content'])
            if "_issues" in result:
                error.append(result['_issues'])
            logger.warning("count, %s: %s, not found: %s", object_type, params, error)
            return 0
        logger.debug("count, search result for %s: status=%s", object_type, result['_status'])

        # If more than one element is found, we get an _items list
        if '_items' in result:
            logger.debug("count, found in the backend: %d %s",
                         len(result['_items']), object_type)
            logger.debug("count, found in the backend: %d total %s",
                         result['_meta']['total'], object_type)
            return result['_meta']['total']

        return 0  # pragma: no cover, simple protection

    def get(self, object_type, params=None, all_elements=False):
        """Get backend elements

        If params is a string, it is considered to be an object id and params
        is modified to {'_id': params}.

        Else, params is used to 'get' objects from the backend.

        Returns an object or an array of matching objects. All extra attributes
        (_links, _status, _meta, ...) are not returned but an '_total' attribute is added
        in each element to get the total count of elements stored in the backend.

        Returns None if the search failed. Do not raise any exception to the caller.

        If all_elements is True, it calls the get_all function of the backend client to
        get all the elements without any pagination activated."""

        logger.debug("get, %s, params: %s", object_type, params)

        if '/' not in object_type:
            # Do not Get a specific element, manage search parameters
            if isinstance(params, string_types):
                params = {'where': {'_id': params}}
                logger.debug("get, %s, params: %s", object_type, params)

            # Update backend search parameters
            if params is None:
                params = {'page': 0, 'max_results': BACKEND_PAGINATION_LIMIT}
            if 'where' in params:
                params['where'] = json.dumps(params['where'])
            if 'embedded' in params:
                params['embedded'] = json.dumps(params['embedded'])
            if 'where' not in params:
                params['where'] = {}
            if 'page' not in params:
                params['page'] = 0
            if 'max_results' not in params:
                params['max_results'] = BACKEND_PAGINATION_LIMIT
        logger.debug("get, search in the backend for %s: parameters=%s, all: %s",
                     object_type, params, all_elements)

        try:
            if all_elements:
                result = self.alignak_backend.get_all(object_type, params=params)
            else:
                result = self.alignak_backend.get(object_type, params=params)
        except BackendException as e:  # pragma: no cover, simple protection
            logger.warning("get, backend exception for %s: %s", object_type, str(e))
            raise BackendException(code=e.code, message=e.message)

        logger.debug("search, search result for %s: result=%s", object_type, result)
        if result['_status'] != 'OK':  # pragma: no cover, should not happen
            error = []
            if "content" in result:
                error.append(result['content'])
            if "_issues" in result:
                error.append(result['_issues'])
            logger.warning("get, %s: %s, not found: %s", object_type, params, error)
            raise ValueError('%s, search: %s was not found in the backend, error: %s'
                             % (object_type, params, error))

        # If more than one element is found, we get an _items list
        if '_items' in result:
            total = len(result['_items'])
            if '_meta' in result:
                total = result['_meta']['total']
            logger.debug("get %s %s, %d total elements found in the backend",
                         object_type, ' (All required)' if all_elements else ' (filtered)', total)
            for item in result['_items']:
                item.update({'_total': total})
            return result['_items']

        if '_status' in result:
            result.pop('_status')
        if '_meta' in result:
            result['_total'] = result['_meta']['total']
            result.pop('_meta')
        if all_elements:
            logger.info("get %s %s, %d total elements found in the backend",
                        object_type, ' (All required)' if all_elements else ' (filtered)',
                        len(result))
            for item in result:
                item.update({'_total': len(result)})
        logger.debug("get, found one in the backend: %s: %s", object_type, result)
        return result

    def post(self, object_type, data=None, files=None):
        """Add an element"""

        logger.info("post, request to add a %s: data: %s", object_type, data)

        # Do not set header to use the client default behavior:
        # - set headers as {'Content-Type': 'application/json'}
        # - encode provided data to JSON
        headers = None
        if files:
            logger.info("post, request to add a %s with files: %s", object_type, files)
            # Set header to disable client default behavior
            headers = {'Content-type': 'multipart/form-data'}

        try:
            result = self.alignak_backend.post(object_type, data=data, files=files, headers=headers)
            logger.debug("post, response: %s", result)
            if result['_status'] != 'OK':
                logger.warning("post, error: %s", result)
                return None
        except BackendException as exp:  # pragma: no cover, simple protection
            logger.exception("post, backend exception: %s", exp)
            error = []
            if getattr(exp, "response", None) and "_issues" in exp.response:
                error = exp.response["_issues"]
            else:
                error.append(str(exp))
            logger.warning("post, error(s): %s", error)
            return error
        except Exception as e:  # pragma: no cover, simple protection
            logger.warning("post, error: %s", str(e))
            return None

        return result['_id']

    def delete(self, object_type, object_id):
        """Delete an element

        - object_type is the element type
        - object_id is the element identifier"""

        logger.info("delete, request to delete the %s: %s", object_type, object_id)

        try:
            # Get most recent version of the element
            element = self.get('/'.join([object_type, object_id]))
            logger.debug("delete, element: %s", element)
        except ValueError:  # pragma: no cover, simple protection
            logger.warning("delete, object %s, _id=%s not found", object_type, object_id)
            return False

        try:
            # Request deletion
            headers = {'If-Match': element['_etag']}
            endpoint = '/'.join([object_type, object_id])
            logger.debug("delete, endpoint: %s", endpoint)
            result = self.alignak_backend.delete(endpoint, headers)
            logger.debug("delete, response: %s", result)
            if result['_status'] != 'OK':  # pragma: no cover, should never happen
                error = []
                if "content" in result:
                    error.append(result["content"])
                if "_issues" in result:
                    error.append(result["_issues"])
                    for issue in result["_issues"]:
                        error.append(result["_issues"][issue])
                logger.warning("delete, error: %s", error)
                return False
        except BackendException as e:  # pragma: no cover, should never happen
            logger.error("delete, backend exception: %s", str(e))
            return False
        except ValueError:  # pragma: no cover, should never happen
            logger.warning("delete, not found %s: %s", object_type, element)
            return False

        return True

    def update(self, element, data):
        """Update an element"""

        logger.info("update, request to update: %s", element)

        try:
            # Request update
            headers = {'If-Match': element['_etag']}
            endpoint = '/'.join([element.get_type(), element.id])
            logger.debug("update, endpoint: %s, data: %s", endpoint, data)
            result = self.alignak_backend.patch(endpoint, data, headers, inception=True)
            logger.debug("update, response: %s", result)
            if result['_status'] != 'OK':  # pragma: no cover, should never happen
                error = []
                if "content" in result:
                    error.append(result["content"])
                if "_issues" in result:
                    error.append(result["_issues"])
                    for issue in result["_issues"]:
                        error.append(result["_issues"][issue])
                logger.warning("update, error(s): %s", error)
                return error
        except BackendException as e:  # pragma: no cover, should never happen
            error = []
            if e.response and "_issues" in e.response:
                error = e.response["_issues"]
            else:
                error.append(str(e))
            logger.error("update, backend exception: %s", str(e))
            return error
        except ValueError:  # pragma: no cover, should never happen
            logger.warning("update, not found %s: %s", element.get_type(), element)
            return False

        return True
    def test_11_domains_and_some_elements(self):
        global backend_address

        print ''
        print 'get all domains and some elements'

        # Create client API
        backend = Backend(backend_address)

        print 'Login ...'
        print 'authenticated:', backend.authenticated
        result = backend.login('admin', 'admin')
        print 'authenticated:', backend.authenticated
        print 'token:', backend.token
        assert_true(backend.authenticated)

        # Get all available endpoints
        print 'get all domains'
        # Filter the templates ...
        items = backend.get_domains()
        print "Got %d elements:" % len(items)
        assert_true('_items2' not in items)
        assert_true(len(items) > 0)
        for item in items:
            assert_true('href' in item)
            assert_true('title' in item)
            print "Domain: ", item

        # Get all hosts
        print 'get all hosts at once'
        # Filter the templates ...
        parameters = { 'where': '{"register":true}', 'max_results': 1 }
        items = backend.get_all('host', params=parameters)
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        # assert_true(len(items) > 0)
        for item in items:
            assert_true('host_name' in item)
            print "Host: ", item['host_name']

        # Get all services
        print 'get all services at once'
        # Filter the templates ...
        parameters = { 'where': '{"register":true}' }
        items = backend.get_all('service', params=parameters)
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        # assert_true(len(items) > 0)
        for item in items:
            assert_true('host_name' in item)
            assert_true('service_description' in item)
            print "Service: %s/%s" % (item['host_name'], item['service_description'])

        # Get all contacts
        print 'get all contacts at once'
        # Filter the templates ...
        parameters = { 'where': '{"register":true}' }
        items = backend.get_all('contact', params=parameters)
        print "Got %d elements:" % len(items)
        assert_true('_items' not in items)
        # assert_true(len(items) > 0)
        for item in items:
            assert_true('contact_name' in item)
            print "Contact: ", item['contact_name']
class BackendClient(object):
    """
        Class who collect informations with Backend-Client and returns data for Alignak-App.
    """

    connection_status = {
        True: 'Success',
        False: 'Failure'
    }

    def __init__(self):
        self.backend = None
        self.connected = False
        self.user = {}
        self.ws_client = WSClient()

    def login(self, username=None, password=None, proxies=None, check=False):
        """
        Connect to alignak backend

        :param username: name or token of user
        :type username: str
        :param password: password of user. If token given, this parameter is useless
        :type password: str
        :param proxies: dictionnary for proxy
        :type proxies: dict
        :param check: define if login is a check or a first login
        :type check: bool
        :return: True if connected or False if not
        :rtype: bool
        """

        # Credentials
        if not username and not password:
            if 'token' in self.user:
                username = self.user['token']

        # Create Backend object
        backend_url = settings.get_config('Alignak', 'backend')
        processes = int(settings.get_config('Alignak', 'processes'))

        self.backend = Backend(backend_url, processes=processes)

        logger.debug('Backend URL : %s', backend_url)
        if not check:
            logger.info('Try to connect to the Alignak backend...')

        if username and password:
            # Username & password : not recommended, without login QDialog
            try:
                self.connected = self.backend.login(username, password, proxies=proxies)
                if self.connected:
                    self.user['username'] = username
                    self.user['token'] = self.backend.token
                logger.info('Connection by password: %s', self.connection_status[self.connected])
            except BackendException:  # pragma: no cover
                logger.error('Connection to Backend has failed !')
        elif username and not password:
            # Username as token : recommended
            if 'token' in self.user:
                self.backend.set_token(self.user['token'])
            else:
                self.backend.set_token(username)
                self.user['token'] = username

            # Make backend connected to test token
            self.connected = True
            connection_test = self.get('user', {'projection': json.dumps({'name': 1})})

            self.connected = bool(connection_test)
            if not check:
                logger.info('Connection by token: %s', self.connection_status[self.connected])
        else:
            logger.warning(
                'Connection to Backend has failed.\n'
                'Check [Alignak] section in configuration file or use login window of application.'
            )

        if self.connected and not check:
            if settings.get_config('Alignak', 'webservice'):
                self.ws_client.login(self.user['token'])
            else:
                logger.info('No configured Web Service.')

        return self.connected

    def get(self, endpoint, params=None, projection=None, all_items=False):
        """
        GET on alignak Backend REST API

        :param endpoint: endpoint (API URL)
        :type endpoint: str
        :param params: dict of parameters for the app_backend API
        :type params: dict|None
        :param projection: list of field to get, if None, get all
        :type projection: list|None
        :param all_items: make GET on all items
        :type all_items: bool
        :return: request response
        :rtype: dict
        """

        request = None

        if self.connected:
            if params is None:
                params = {'max_results': 50}
            if projection is not None:
                generate_proj = {}
                for field in projection:
                    generate_proj[field] = 1
                params['projection'] = json.dumps(generate_proj)
            # Request
            try:
                if not all_items:
                    request = self.backend.get(
                        endpoint,
                        params
                    )
                else:
                    request = self.backend.get_all(
                        endpoint,
                        params
                    )
                logger.info('GET on [%s] backend > %s', endpoint, str(request['_status']))
                logger.debug('\tparams: [%s]', str(params))
            except BackendException:
                self.connected = False
        else:
            logger.info('App is not connected to backend !')

        return request

    def post(self, endpoint, data, headers=None):  # pragma: no cover - Post already test by client
        """
        POST on alignak Backend REST API

        :param endpoint: endpoint (API URL)
        :type endpoint: str
        :param data: properties of item to create | add
        :type data: dict
        :param headers: headers (example: Content-Type)
        :type headers: dict|None
        :return: response (creation information)
        :rtype: dict
        """

        request = None

        if self.connected:
            try:
                request = self.backend.post(endpoint, data, headers=headers)
                logger.info('POST on [%s] backend > %s', endpoint, str(request['_status']))
                logger.debug('\tdata: [%s]', str(data))
                logger.debug('\theaders: [%s]', str(headers))
            except BackendException:
                self.connected = False
        else:
            logger.info('App is not connected to backend !')

        return request

    def patch(self, endpoint, data, headers):
        """
        PATCH on alignak Backend REST API

        :param endpoint: endpoint (API URL)
        :type endpoint: str
        :param data: properties of item to update
        :type data: dict
        :param headers: headers (example: Content-Type). 'If-Match' required
        :type headers: dict
        :return: dictionary containing patch response from the backend
        :rtype: dict
        """

        request = None

        if self.connected:
            try:
                request = self.backend.patch(endpoint, data, headers=headers, inception=True)
                logger.info('PATCH on [%s] backend > %s', endpoint, str(request['_status']))
                logger.debug('\tdata: [%s]', str(data))
                logger.debug('\theaders: [%s]', str(headers))
            except BackendException:
                self.connected = False
        else:
            logger.info('App is not connected to backend !')

        return request

    def acknowledge(self, item, sticky, notify, comment):  # pragma: no cover
        """
        Prepare data for acknowledge and POST on backend API or WS if available

        :param item: item to acknowledge: host | service
        :type item: alignak_app.items.host.Host | alignak_app.items.service.Service
        :param sticky: define if ack is sticky or not
        :type sticky: bool
        :param notify: define if ack should notify user or not
        :type notify: bool
        :param comment: comment of ack
        :type comment: str
        :return: request response
        :rtype: dict
        """

        user = data_manager.database['user']

        if self.ws_client.auth:
            if item.item_type == 'service':
                command = 'ACKNOWLEDGE_SVC_PROBLEM'
                host = data_manager.get_item('host', '_id', item.data['host'])
                element = host.name
            else:
                command = 'ACKNOWLEDGE_HOST_PROBLEM'
                element = item.name
            item_name = item.name
            if sticky:
                sticky = '2'
            else:
                sticky = '1'
            notify = str(int(notify))
            persistent = '0'

            parameters = ';'.join([item_name, sticky, notify, persistent, user.name, comment])
            data = {
                'command': command,
                'element': element,
                'parameters': parameters
            }
            request = self.ws_client.post('command', params=data)
        else:
            data = {
                'action': 'add',
                'user': user.item_id,
                'comment': comment,
                'notify': notify,
                'sticky': sticky
            }
            if item.item_type == 'service':
                data['host'] = item.data['host']
                data['service'] = item.item_id
            else:
                data['host'] = item.item_id
                data['service'] = None

            request = self.post('actionacknowledge', data)

        return request

    # pylint: disable=too-many-arguments
    def downtime(self, item, fixed, duration, start_stamp, end_stamp, comment):  # pragma: no cover
        """
        Prepare data for downtime and POST on backend API or WS if available

        :param item: item to downtime: host | service
        :type item: alignak_app.items.host.Host | alignak_app.items.service.Service
        :param fixed: define if donwtime is fixed or not
        :type fixed: bool
        :param duration: duration timestamp of downtime
        :type duration: int
        :param start_stamp: start timestamp of downtime
        :type start_stamp: int
        :param end_stamp: end timestamp of downtime
        :type end_stamp: int
        :param comment: comment of downtime
        :type comment: str
        :return: request response
        :rtype: dict
        """

        if self.ws_client.auth:
            if item.item_type == 'service':
                host = data_manager.get_item('host', '_id', item.data['host'])
                element = host.name
            else:
                element = item.name
            fixed = str(int(fixed))
            item_name = item.name
            trigger_id = '0'
            parameters = ';'.join(
                [item_name, str(start_stamp), str(end_stamp), fixed, trigger_id, str(duration),
                 data_manager.database['user'].name, comment]
            )
            data = {
                'command': 'SCHEDULE_SVC_DOWNTIME' if item.item_type == 'service' else
                           'SCHEDULE_HOST_DOWNTIME',
                'element': element,
                'parameters': parameters
            }
            request = self.ws_client.post('command', params=data)
        else:
            data = {
                'action': 'add',
                'user': data_manager.database['user'].item_id,
                'fixed': fixed,
                'duration': duration,
                'start_time': start_stamp,
                'end_time': end_stamp,
                'comment': comment,
            }

            if item.item_type == 'service':
                data['host'] = item.data['host']
                data['service'] = item.item_id
            else:
                data['host'] = item.item_id
                data['service'] = None

            request = app_backend.post('actiondowntime', data)

        return request

    def query_realms(self):
        """
        Launch a request on ``realm`` endpoint

        """

        request_model = Realm.get_request_model()

        request = self.get(
            request_model['endpoint'],
            request_model['params'],
            request_model['projection']
        )

        if request:
            realms_list = []
            for backend_item in request['_items']:
                realm = Realm()

                realm.create(
                    backend_item['_id'],
                    backend_item,
                    backend_item['name'],
                )
                realms_list.append(realm)

            if realms_list:
                data_manager.update_database('realm', realms_list)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_timeperiods(self):
        """
        Launch a request on ``timeperiod`` endpoint

        """

        request_model = Period.get_request_model()

        request = self.get(
            request_model['endpoint'],
            request_model['params'],
            request_model['projection']
        )

        if request:
            periods_list = []
            for backend_item in request['_items']:
                period = Period()

                period.create(
                    backend_item['_id'],
                    backend_item,
                    backend_item['name'],
                )
                periods_list.append(period)

            if periods_list:
                data_manager.update_database('timeperiod', periods_list)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_user(self):
        """
        Launch request on "user" endpoint. Only for current App user.

        """

        request_model = User.get_request_model(self.backend.token)

        request = self.get(
            request_model['endpoint'],
            request_model['params'],
            request_model['projection']
        )

        if request:
            if request['_items']:
                user = User()

                user.create(
                    request['_items'][0]['_id'],
                    request['_items'][0],
                    request['_items'][0]['name']
                )

                data_manager.update_database('user', user)

            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_hosts(self):
        """
        Launch request on "host" endpoint, add hosts in problems if needed

        """

        request_model = Host.get_request_model()

        request = self.get(
            request_model['endpoint'],
            request_model['params'],
            request_model['projection'],
            all_items=True
        )

        if request:
            hosts_list = []
            for backend_item in request['_items']:
                host = Host()

                host.create(
                    backend_item['_id'],
                    backend_item,
                    backend_item['name'],
                )
                hosts_list.append(host)

                # If host is a problem, add / update it
                if data_manager.is_problem('host', backend_item):
                    if data_manager.get_item('problems', host.item_id):
                        data_manager.update_item_data('problems', host.item_id, host.data)
                    else:
                        data_manager.database['problems'].append(host)

            data_manager.db_is_ready['problems']['host'] = True

            if hosts_list:
                data_manager.update_database('host', hosts_list)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_services(self, host_id=None):
        """
        Launch request for "service" endpoint. If ``host_id`` is given, only services related to
        host are added / updated

        :param host_id: "_id" of host
        :type host_id: str
        """

        request_model = Service.get_request_model(host_id)

        request = self.get(
            request_model['endpoint'],
            request_model['params'],
            request_model['projection'],
            all_items=True
        )

        if request:
            services_list = []
            for backend_item in request['_items']:
                service = Service()

                service.create(
                    backend_item['_id'],
                    backend_item,
                    backend_item['name'],
                )

                # Add / update only services of host "if host_id"
                if host_id:
                    if not data_manager.get_item('service', service.item_id):
                        logger.debug('Add item data in database[service]')
                        data_manager.database['service'].append(service)
                    else:
                        data_manager.update_item_data('service', service.item_id, service.data)

            # If not item ID, update all database
            if services_list and not host_id:
                data_manager.update_database('service', services_list)
            if host_id:
                host = data_manager.get_item('host', '_id', host_id)
                if host:
                    logger.info('Update database[service] for %s', host.name)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_services_problems(self, state):
        """
        Launch requests on "service" endpoint to get items with "ls_state = state"

        Wanted states are: ``WARNING``, ``CRITICAL`` and ``UNKNOWN``

        :param state: state of service
        :type state: str
        """

        # Services
        services_projection = [
            'name', 'host', 'alias', 'ls_state', 'ls_output', 'ls_acknowledged', 'ls_downtimed',
            'passive_checks_enabled', 'active_checks_enabled'
        ]

        params = {'where': json.dumps({'_is_template': False, 'ls_state': state})}
        request = self.get(
            'service',
            params,
            services_projection,
            all_items=True
        )

        if request:
            for backend_item in request['_items']:
                if data_manager.is_problem('service', backend_item):
                    service = Service()
                    service.create(
                        backend_item['_id'],
                        backend_item,
                        backend_item['name']
                    )

                    if data_manager.get_item('problems', service.item_id):
                        data_manager.update_item_data('problems', service.item_id, service.data)
                    else:
                        data_manager.database['problems'].append(service)
            # Problems state is ready
            data_manager.db_is_ready['problems'][state] = True
            logger.info("Update database[problems] for %s services...", state)

    def query_alignakdaemons(self):
        """
        Launch request on "alignakdaemon" endpoint

        """

        request_model = Daemon.get_request_model()

        request = self.get(
            request_model['endpoint'],
            request_model['params'],
            request_model['projection'],
            all_items=True
        )

        if request:
            daemons_list = []
            for backend_item in request['_items']:
                daemon = Daemon()

                daemon.create(
                    backend_item['_id'],
                    backend_item,
                    backend_item['name'],
                )

                daemons_list.append(daemon)

            if daemons_list:
                data_manager.update_database('alignakdaemon', daemons_list)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_livesynthesis(self):
        """
        Launch request on "livesynthesis" endpoint

        """

        request_model = LiveSynthesis.get_request_model()

        request = self.get(
            request_model['endpoint'],
            request_model['params'],
            request_model['projection'],
            all_items=True
        )

        if request:
            livesynthesis = []
            for backend_item in request['_items']:
                synthesis = LiveSynthesis()

                synthesis.create(
                    backend_item['_id'],
                    backend_item,
                )

                livesynthesis.append(synthesis)

            if livesynthesis:
                data_manager.update_database('livesynthesis', livesynthesis)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_history(self, hostname=None, host_id=None):
        """
        Launch request on "history" endpoint but only for hosts in "data_manager"

        :param hostname: name of host we want history
        :type hostname: str
        :param host_id: id of host for history
        :type host_id: str
        """

        request_model = History.get_request_model()

        if hostname and host_id:
            request_model['params']['where'] = json.dumps({
                'host': host_id})
            request_model['params']['max_results'] = 25

            request = self.get(
                request_model['endpoint'],
                request_model['params'],
                request_model['projection'],
                all_items=False
            )

            if request:
                logger.debug('Add / Update history for %s (%s)', hostname, host_id)
                if data_manager.get_item('history', host_id):
                    data_manager.update_item_data('history', host_id, request['_items'])
                else:
                    host_history = History()

                    host_history.create(
                        host_id,
                        request['_items'],
                        hostname,
                    )
                    data_manager.database['history'].append(host_history)
        else:  # pragma: no cover, too long to test
            history_list = []
            for history in data_manager.database['history']:
                request_model['params']['where'] = json.dumps({
                    'host': history.item_id})
                request_model['params']['max_results'] = 25

                request = self.get(
                    request_model['endpoint'],
                    request_model['params'],
                    request_model['projection'],
                    all_items=False
                )

                if request:
                    host_history = History()

                    host_history.create(
                        history.item_id,
                        request['_items'],
                        history.name,
                    )
                    history_list.append(host_history)

            if history_list:
                data_manager.update_database('history', history_list)

    def query_notifications(self):  # pragma: no cover, notifications can be empty
        """
        Launch request on "history" endpoint.
        Only for 'type': 'monitoring.notification' and for current App user

        """

        request_model = Event.get_request_model()

        request = self.get(
            request_model['endpoint'],
            request_model['params'],
            request_model['projection'],
            all_items=False
        )

        if request:
            notifications = []
            for backend_item in request['_items']:
                message_split = backend_item['message'].split(';')
                user = message_split[0].split(':')[1].strip()
                if 'imported_admin' in user:
                    user = '******'
                if user == data_manager.database['user'].name:
                    notification = Event()

                    notification.create(
                        backend_item['_id'],
                        backend_item,
                    )

                    notifications.append(notification)

            if notifications:
                data_manager.update_database('notifications', notifications)

    def get_backend_status_icon(self):
        """
        Return backend status icon name

        :return: status icon name
        :rtype: str
        """

        if self.connected:
            return Daemon.get_states('ok')

        return Daemon.get_states('ko')

    def get_ws_status_icon(self):
        """
        Return Web Service status icon name

        :return: status icon name
        :rtype: str
        """

        if self.ws_client.auth:
            return Daemon.get_states('ok')

        return Daemon.get_states('ko')
示例#15
0
class AlignakBackendScheduler(BaseModule):
    """
    This class is used to send live states to alignak-backend
    """
    def __init__(self, mod_conf):
        """Module initialization

        mod_conf is a dictionary that contains:
        - all the variables declared in the module configuration file
        - a 'properties' value that is the module properties as defined globally in this file

        :param mod_conf: module configuration file as a dictionary
        """
        BaseModule.__init__(self, mod_conf)

        # pylint: disable=global-statement
        global logger
        logger = logging.getLogger('alignak.module.%s' % self.alias)
        logger.setLevel(getattr(mod_conf, 'log_level', logging.INFO))

        logger.debug("inner properties: %s", self.__dict__)
        logger.debug("received configuration: %s", mod_conf.__dict__)

        self.client_processes = int(getattr(mod_conf, 'client_processes', 1))
        logger.info("Number of processes used by backend client: %s",
                    self.client_processes)

        self.backend_count = int(getattr(mod_conf, 'backend_count', '50'))
        logger.info("backend pagination count: %d items", self.backend_count)

        logger.info("StatsD configuration: %s:%s, prefix: %s, enabled: %s",
                    getattr(mod_conf, 'statsd_host', 'localhost'),
                    int(getattr(mod_conf, 'statsd_port', '8125')),
                    getattr(mod_conf, 'statsd_prefix', 'alignak'),
                    (getattr(mod_conf, 'statsd_enabled', '0') != '0'))
        self.statsmgr = Stats()
        self.statsmgr.register(
            self.alias,
            'module',
            statsd_host=getattr(mod_conf, 'statsd_host', 'localhost'),
            statsd_port=int(getattr(mod_conf, 'statsd_port', '8125')),
            statsd_prefix=getattr(mod_conf, 'statsd_prefix', 'alignak'),
            statsd_enabled=(getattr(mod_conf, 'statsd_enabled', '0') != '0'))

        self.url = getattr(mod_conf, 'api_url', 'http://localhost:5000')
        logger.info("Alignak backend endpoint: %s", self.url)
        self.backend = Backend(self.url, self.client_processes)
        self.backend.token = getattr(mod_conf, 'token', '')
        self.backend_connected = False
        self.backend_errors_count = 0
        self.backend_username = getattr(mod_conf, 'username', '')
        self.backend_password = getattr(mod_conf, 'password', '')
        self.backend_generate = getattr(mod_conf, 'allowgeneratetoken', False)

        if not self.backend.token:
            logger.warning(
                "no user token configured. "
                "It is recommended to set a user token rather than a user login "
                "in the configuration. Trying to get a token from the provided "
                "user login information...")
            self.getToken()
        else:
            self.backend_connected = True

    # Common functions
    def do_loop_turn(self):
        """This function is called/used when you need a module with
        a loop function (and use the parameter 'external': True)
        """
        logger.info("[Backend Scheduler] In loop")
        time.sleep(1)

    def getToken(self):
        """Authenticate and get the token

        :return: None
        """
        generate = 'enabled'
        if not self.backend_generate:
            generate = 'disabled'

        try:
            start = time.time()
            self.backend_connected = self.backend.login(
                self.backend_username, self.backend_password, generate)
            self.statsmgr.counter('backend-login', 1)
            self.statsmgr.timer('backend-login-time', time.time() - start)
            if not self.backend_connected:
                logger.warning("Backend login failed")
            self.token = self.backend.token
            self.backend_errors_count = 0
        except BackendException as exp:  # pragma: no cover - should not happen
            self.backend_connected = False
            self.backend_errors_count += 1
            logger.warning(
                "Alignak backend is not available for login. "
                "No backend connection, attempt: %d",
                self.backend_errors_count)
            logger.debug("Exception: %s", exp)

    def raise_backend_alert(self, errors_count=10):
        """Raise a backend alert

        :return: True if the backend is not connected and the error count
        is greater than a defined threshold
        """
        logger.debug(
            "Check backend connection, connected: %s, errors count: %d",
            self.backend_connected, self.backend_errors_count)
        if not self.backend_connected and self.backend_errors_count >= errors_count:
            return True

        return False

    def hook_load_retention(self, scheduler):
        """Load retention data from alignak-backend

        :param scheduler: scheduler instance of alignak
        :type scheduler: object
        :return: None
        """

        all_data = {'hosts': {}, 'services': {}}

        if not self.backend_connected:
            self.getToken()
            if self.raise_backend_alert(errors_count=1):
                logger.warning("Alignak backend connection is not available. "
                               "Loading retention data is not possible.")
                return None

        if not self.backend_connected:
            return None

        # Get data from the backend
        try:
            start = time.time()
            params = {"max_results": self.backend_count}
            response = self.backend.get_all('alignakretention', params)
            for host in response['_items']:
                # clean unusable keys
                hostname = host['host']
                if 'retention_services' in host:
                    for service in host['retention_services']:
                        all_data['services'][(host['host'], service)] = \
                            host['retention_services'][service]
                for key in [
                        '_created', '_etag', '_id', '_links', '_updated',
                        'host', 'retention_services', '_user', 'schema_version'
                ]:
                    if key in host:
                        del host[key]
                all_data['hosts'][hostname] = host

            logger.info('%d hosts loaded from retention',
                        len(all_data['hosts']))
            self.statsmgr.counter('retention-load.hosts',
                                  len(all_data['hosts']))
            logger.info('%d services loaded from retention',
                        len(all_data['services']))
            self.statsmgr.counter('retention-load.services',
                                  len(all_data['services']))
            self.statsmgr.timer('retention-load.time', time.time() - start)

            scheduler.restore_retention_data(all_data)
        except BackendException:
            self.backend_connected = False
            self.backend_errors_count += 1
            logger.warning(
                "Alignak backend connection fails. Check and fix your configuration"
            )
            return False

        return True

    def hook_save_retention(self, scheduler):
        """Save retention data to alignak-backend

        :param scheduler: scheduler instance of alignak
        :type scheduler: object
        :return: None
        """
        if not self.backend_connected:
            self.getToken()
            if self.raise_backend_alert(errors_count=1):
                logger.warning("Alignak backend connection is not available. "
                               "Saving objects is not possible.")
                return None

        if not self.backend_connected:
            return None

        try:
            data_to_save = scheduler.get_retention_data()
            start_time = time.time()

            # get list of retention_data
            params = {"max_results": self.backend_count}
            response = self.backend.get_all('alignakretention', params)
            db_hosts = {}
            for host in response['_items']:
                db_hosts[host['host']] = host

            # add services in the hosts
            for host in data_to_save['hosts']:
                data_to_save['hosts'][host]['retention_services'] = {}
                data_to_save['hosts'][host]['host'] = host
            if 'services' in data_to_save:
                # Scheduler old-school: two separate dictionaries!
                for service in data_to_save['services']:
                    data_to_save['hosts'][service[0]]['retention_services'][service[1]] = \
                        data_to_save['services'][service]

            for host in data_to_save['hosts']:
                if host in db_hosts:
                    # if host in retention_data, PUT
                    headers = {'Content-Type': 'application/json'}
                    headers['If-Match'] = db_hosts[host]['_etag']
                    try:
                        logger.debug('Host retention data: %s',
                                     data_to_save['hosts'][host])
                        self.backend.put(
                            'alignakretention/%s' % (db_hosts[host]['_id']),
                            data_to_save['hosts'][host], headers, True)
                    except BackendException as exp:  # pragma: no cover - should not happen
                        logger.error('Put alignakretention error')
                        logger.error('Response: %s', exp.response)
                        logger.exception("Exception: %s", exp)
                        self.backend_connected = False
                        return False
                else:
                    # if not host in retention_data, POST
                    try:
                        logger.debug('Host retention data: %s',
                                     data_to_save['hosts'][host])
                        self.backend.post('alignakretention',
                                          data=data_to_save['hosts'][host])
                    except BackendException as exp:  # pragma: no cover - should not happen
                        logger.error('Post alignakretention error')
                        logger.error('Response: %s', exp.response)
                        logger.exception("Exception: %s", exp)
                        self.backend_connected = False
                        return False
            logger.info('%d hosts saved in retention',
                        len(data_to_save['hosts']))
            self.statsmgr.counter('retention-save.hosts',
                                  len(data_to_save['hosts']))
            logger.info('%d services saved in retention',
                        len(data_to_save['services']))
            self.statsmgr.counter('retention-save.services',
                                  len(data_to_save['services']))
            self.statsmgr.timer('retention-save.time',
                                time.time() - start_time)

            now = time.time()
            logger.info("Retention saved in %s seconds", (now - start_time))
        except BackendException:
            self.backend_connected = False
            self.backend_errors_count += 1
            logger.warning(
                "Alignak backend connection fails. Check and fix your configuration"
            )
            return False

        return True
示例#16
0
class DataManager(object):
    '''
    Base class for all data manager objects
    '''
    id = 1

    """
        Application data manager object
    """

    def __init__(self, backend_endpoint='http://127.0.0.1:5000', glpi=None):
        """
        Create an instance
        """
        # Set a unique id for each DM object
        self.__class__.id += 1

        # Associated backend object
        self.backend_endpoint = backend_endpoint
        self.backend = Backend(backend_endpoint)

        # Associated Glpi backend object
        self.glpi = None
        if glpi:
            self.glpi = Glpi(glpi.get('glpi_ws_backend', None))
            self.glpi_ws_login = glpi.get('glpi_ws_login', None)
            self.glpi_ws_password = glpi.get('glpi_ws_password', None)

        # Backend available objects (filled with objects received from backend)
        # self.backend_available_objets = []

        # Get known objects type from the imported modules
        # Search for classes including an _type attribute
        self.known_classes = []
        for k, v in globals().items():
            if isinstance(globals()[k], type) and '_type' in globals()[k].__dict__:
                self.known_classes.append(globals()[k])
                logger.debug(
                    "Known class %s for object type: %s",
                    globals()[k], globals()[k].getType()
                )

        self.connected = False
        self.logged_in_user = None
        self.connection_message = None
        self.loading = 0
        self.loaded = False

        self.refresh_required = False
        self.refresh_done = False

        self.updated = datetime.utcnow()

    def __repr__(self):
        return ("<DM, id: %s, objects count: %d, user: %s, updated: %s>") % (
            self.id,
            self.get_objects_count(),
            self.get_logged_user().get_username() if self.get_logged_user() else 'Not logged in',
            self.updated
        )

    ##
    # Connected user
    ##
    def user_login(self, username, password=None, load=True):
        """
        Set the data manager user

        If password is provided, use the backend login function to authenticate the user

        If no password is provided, the username is assumed to be an authentication token and we
        use the backend connect function.
        """
        logger.info("user_login, connection requested: %s, load: %s", username, load)

        self.connection_message = _('Backend connecting...')
        if not password:
            # Set backend token (no login request).
            # Do not include the token in the application logs !
            logger.debug("Update backend token")
            self.backend.token = username
            self.connected = True
            self.connection_message = _('Backend connected')
            # Load data if load required...
            if load:
                self.load(reset=True)
            return self.connected

        try:
            # Backend real login
            logger.info("Requesting backend authentication, username: %s", username)
            self.connected = self.backend.login(username, password)
            if self.connected:
                self.connection_message = _('Connection successful')

                # Fetch the logged-in user
                users = self.find_object(
                    'contact', {'where': {'token': self.backend.token}}
                )
                # Tag user as authenticated
                users[0].authenticated = True
                self.logged_in_user = users[0]

                # Get total objects count from the backend
                objects_count = self.get_objects_count(refresh=True, log=True)

                # Load data if load required...
                if load:
                    self.load(reset=True)
            else:
                self.connection_message = _('Backend connection refused...')
        except BackendException as e:  # pragma: no cover, should not happen
            logger.warning("configured applications backend is not available!")
            self.connection_message = e.message
            self.connected = False
        except Exception as e:  # pragma: no cover, should not happen
            logger.warning("User login exception: %s", str(e))
            logger.error("traceback: %s", traceback.format_exc())

        logger.info("user_login, connection message: %s", self.connection_message)
        return self.connected

    def user_logout(self):
        """
        Logout the data manager user. Do not log-out from the backend. Need to reset the
        datamanager to do it.
        """
        self.logged_in_user = None

    def get_logged_user(self):
        """
        Get the current logged in user
        """
        return self.logged_in_user

    ##
    # Find objects and load objects cache
    ##
    def find_object(self, object_type, params=None, all_elements=False):
        """
        Find an object ...

        Search in internal objects cache for an object matching the required parameters

        If params is a string, it is considered to be an object id and params
        is modified to {'_id': params}.

        Else, params is a dictionary of key/value to find a matching object in the objects cache
        If no objects are found in the cache, params is user to 'get' objects from the backend.

        Default behavior is to search in the backend if objects are not found in the cache. Call
        with backend=False to search only in local cache.

        If the backend search is successful, a new object is created if it exists a class in the
        imported modules (presumably alignak_webui.objects.item) which contains a 'bo_type' property
        and this property is valued as 'object_type'.

        Returns an array of matching objects
        """
        logger.debug("find_object, self: %s, updated:%s", self, self.updated)
        logger.debug("find_object, %s, params: %s", object_type, params)

        # -----------------------------------------------------------------------------------------
        # TODO: manage parameters like:
        # {'sort': '-opening_date', 'where': u'{"service_name": "userservice_1"}'}
        # -> ignore sort, ... but take car of 'where' to search in the cache!
        # -----------------------------------------------------------------------------------------
        # if params is None:
        # params = {'page': 0, 'max_results': BACKEND_PAGINATION_LIMIT}

        unique_element = False
        if isinstance(params, basestring):
            params = {'where': {'_id': params}}
            unique_element = True
            logger.debug("find_object, %s, params: %s", object_type, params)

        items = []

        # Update backend search parameters
        if params is None:
            params = {'page': 0, 'max_results': BACKEND_PAGINATION_LIMIT}
        if 'where' in params:
            params['where'] = json.dumps(params['where'])
        if 'embedded' in params:
            params['embedded'] = json.dumps(params['embedded'])
        if 'where' not in params:
            params['where'] = {}
        if 'page' not in params:
            params['page'] = 0
        if 'max_results' not in params:
            params['max_results'] = BACKEND_PAGINATION_LIMIT
        logger.debug(
            "find_object, search in the backend for %s: parameters=%s", object_type, params
        )

        try:
            if all_elements:
                result = self.backend.get_all(object_type, params=params)
            else:
                result = self.backend.get(object_type, params=params)
        except BackendException as e:  # pragma: no cover, simple protection
            logger.warning("find_object, backend exception: %s", str(e))
            return items

        logger.debug(
            "find_object, search result for %s: result=%s", object_type, result
        )
        if result['_status'] != 'OK':  # pragma: no cover, should not happen
            error = []
            if "content" in result:
                error.append(result["content"])
            if "_issues" in result:
                error.append(result["_issues"])
            logger.warning("find_object, %s: %s, not found: %s", object_type, params, error)
            raise ValueError(
                '%s, where %s was not found in the backend, error: %s' % (
                    object_type, params, error
                )
            )

        if not result['_items']:  # pragma: no cover, should occur rarely
            if items:
                logger.debug(
                    "find_object, no data in backend, found in the cache: %s: %s",
                    object_type, items
                )
                return items
            logger.debug(
                "find_object, %s: %s: not found: %s", object_type, params, result
            )
            raise ValueError(
                '%s, %s was not found in the cache nor in the backend' % (
                    object_type, params['where']
                )
            )

        logger.debug("find_object, found in the backend: %s: %s", object_type, result['_items'])
        for item in result['_items']:
            # Find "Backend object type" classes in file imported modules ...
            for k, v in globals().items():
                if isinstance(globals()[k], type) and '_type' in globals()[k].__dict__:
                    if globals()[k].getType() == object_type:
                        # Create a new object
                        bo_object = globals()[k](item)
                        items.append(bo_object)
                        self.updated = datetime.utcnow()
                        logger.debug("find_object, created: %s", bo_object)
                        break
            else:  # pragma: no cover, should not happen
                # No break, so not found a relative class!
                logger.error("find_object, %s class not found for: %s", object_type, item)

        return items

    def load(self, reset=False, refresh=False):
        """
            Load data in the data manager objects

            If reset is set, then all the existing objects are deleted and then created from
            scratch (first load ...). Else, existing objects are updated and new objects are
            created.

            Get all the users (related to current logged-in user)
            Get all the user services (related to current logged-in user)
            Get all the relations between users and services
            Get the most recent sessions for each user service

            :returns: the number of newly created objects
        """
        if not self.get_logged_user():
            logger.error("load, must be logged-in before loading")
            return False

        if self.loading > 0:  # pragma: no cover, protection if application shuts down ...
            logger.error("load, already loading: trial: %d", self.loading)
            if self.loading < 3:
                self.loading += 1
                return False
            logger.error("load, already loading: reset counter")
            self.loading = 0

        logger.debug("load, start loading: %s for %s", self, self.get_logged_user())
        logger.debug(
            "load, start as administrator: %s", self.get_logged_user().is_administrator()
        )
        start = time.time()

        if reset:
            logger.warning("Objects cache reset")
            self.reset(logout=False)

        self.loading += 1
        self.loaded = False

        # Get internal objects count
        objects_count = self.get_objects_count()
        logger.debug("Load, start, objects in cache: %d", objects_count)

        # -----------------------------------------------------------------------------------------
        # Get all users if current user is an administrator
        # -----------------------------------------------------------------------------------------
        self.get_users()

        # -----------------------------------------------------------------------------------------
        # Get all commands
        # -----------------------------------------------------------------------------------------
        commands = self.get_commands()

        # -----------------------------------------------------------------------------------------
        # Get all hosts
        # -----------------------------------------------------------------------------------------
        hosts = self.get_hosts()

        # Get internal objects count
        new_objects_count = self.get_objects_count()
        logger.debug("Load, end, objects in cache: %d", new_objects_count)

        logger.warning(
            "Data manager load (%s), new objects: %d,  duration: %s",
            refresh, new_objects_count - objects_count, (time.time() - start)
        )

        if new_objects_count > objects_count:
            self.require_refresh()

        self.loaded = True
        self.loading = 0
        return new_objects_count - objects_count

    def reset(self, logout=False):
        """
        Reset data in the data manager objects
        """
        logger.info("Data manager reset...")

        # Clean internal objects cache
        for known_class in self.known_classes:
            logger.info("Cleaning %s cache...", known_class.getType())
            known_class.cleanCache()

        if logout:
            self.backend.logout()
            self.user_logout()

        self.loaded = False
        self.loading = 0
        self.refresh_required = True

    def require_refresh(self):
        '''
        Require an immediate refresh
        '''
        self.refresh_required = True
        self.refresh_done = False

    def get_objects_count(self, object_type=None, refresh=False, log=False, search=None):
        '''
        Get the count of the objects stored in the data manager cache

        If an object_type is specified, only returns the count for this object type

        If refresh is True, get the total count from the backend. This is only useful if total
        count is required...

        If log is set, an information log is made
        '''
        log_function = logger.debug
        if log:
            log_function = logger.info

        if object_type:
            for known_class in self.known_classes:
                if object_type == known_class.getType():
                    objects_count = known_class.getCount()
                    log_function(
                        "get_objects_count, currently %d cached %ss",
                        objects_count, object_type
                    )
                    if refresh:
                        if hasattr(known_class, '_total_count'):
                            objects_count = known_class.getTotalCount()
                            log_function(
                                "get_objects_count, got _total_count attribute: %d",
                                objects_count
                            )
                        else:
                            objects_count = self.count_objects(object_type, search=search)

                        log_function(
                            "get_objects_count, currently %d total %ss for %s",
                            objects_count, object_type, search
                        )
                    return objects_count
            else:  # pragma: no cover, should not happen
                logger.warning("count_objects, unknown object type: %s", object_type)
                return 0

        objects_count = 0
        for known_class in self.known_classes:
            count = known_class.getCount()
            log_function(
                "get_objects_count, currently %d cached %ss",
                count, known_class.getType()
            )
            if refresh:
                count = self.count_objects(known_class.getType(), search=search)
                log_function(
                    "get_objects_count, currently %d total %ss for %s",
                    count, known_class.getType(), search
                )
            objects_count += count

        log_function("get_objects_count, currently %d elements", objects_count)
        return objects_count

    ##
    #
    # Elements add, delete, update, ...
    ##
    def count_objects(self, object_type, search=None):
        """
        Request objects from the backend to pick-up total records count.
        Make a simple request for 1 element and we will get back the total count of elements

        search is a dictionary of key / value to search for

        If log is set, an information log is made
        """
        total = 0

        params = {
            'page': 0, 'max_results': 1
        }
        if search is not None:
            params['where'] = json.dumps(search)

        # Request objects from the backend ...
        try:
            resp = self.backend.get(object_type, params)
            logger.debug("count_objects %s: %s", object_type, resp)

            # Total number of records
            if '_meta' in resp:
                total = int(resp['_meta']['total'])
        except BackendException as e:
            logger.warning(
                "count_objects exception for object type: %s: %s",
                object_type, e.message
            )

        return total

    def add_object(self, object_type, data=None, files=None):
        """ Add an element """
        logger.info("add_object, request to add a %s: data: %s", object_type, data)

        # Do not set header to use the client default behavior:
        # - set headers as {'Content-Type': 'application/json'}
        # - encode provided data to JSON
        headers = None
        if files:
            logger.info("add_object, request to add a %s with files: %s", object_type, files)
            # Set header to disable client default behavior
            headers = {'Content-type': 'multipart/form-data'}

        try:
            result = self.backend.post(object_type, data=data, files=files, headers=headers)
            logger.debug("add_object, response: %s", result)
            if result['_status'] != 'OK':
                logger.warning("add_object, error: %s", result)
                return None

            self.find_object(object_type, result['_id'])
        except BackendException as e:
            logger.error("add_object, backend exception: %s", str(e))
            return None
        except ValueError as e:  # pragma: no cover, should never happen
            logger.warning("add_object, error: %s", str(e))
            return None

        return result['_id']

    def delete_object(self, object_type, element):
        """
        Delete an element
        - object_type is the element type
        - element may be a string. In this case it is considered to be the element id
        """
        logger.info("delete_object, request to delete the %s: %s", object_type, element)

        if isinstance(element, basestring):
            object_id = element
        else:
            object_id = element.get_id()

        try:
            # Get most recent version of the element
            items = self.find_object(object_type, object_id)
            element = items[0]
        except ValueError:  # pragma: no cover, should never happen
            logger.warning("delete_object, object %s, _id=%s not found", object_type, object_id)
            return False

        try:
            # Request deletion
            headers = {'If-Match': element['_etag']}
            endpoint = '/'.join([object_type, object_id])
            logger.info("delete_object, endpoint: %s", endpoint)
            result = self.backend.delete(endpoint, headers)
            logger.debug("delete_object, response: %s", result)
            if result['_status'] != 'OK':  # pragma: no cover, should never happen
                error = []
                if "content" in result:
                    error.append(result["content"])
                if "_issues" in result:
                    error.append(result["_issues"])
                    for issue in result["_issues"]:
                        error.append(result["_issues"][issue])
                logger.warning("delete_object, error: %s", error)
                return False
        except BackendException as e:  # pragma: no cover, should never happen
            logger.error("delete_object, backend exception: %s", str(e))
            return False
        except ValueError as e:  # pragma: no cover, should never happen
            logger.warning("delete_object, not found %s: %s", object_type, element)
            return False

        try:
            # Try to get most recent version of the element
            items = self.find_object(object_type, object_id)
        except ValueError:
            logger.info("delete_object, object deleted: %s, _id=%s", object_type, object_id)
            # Object deletion
            element._delete()

        return True

    def update_object(self, object_type, element, data):
        """
        Update an element
        - object_type is the element type
        - element may be a string. In this case it is considered to be the element id
        """
        logger.info("update_object, request to update the %s: %s", object_type, element)

        if isinstance(element, basestring):
            object_id = element
        else:
            object_id = element.get_id()

        try:
            # Get most recent version of the element
            items = self.find_object(object_type, object_id)
            element = items[0]
        except ValueError:
            logger.warning("update_object, object %s, _id=%s not found", object_type, object_id)
            return False

        try:
            # Request update
            headers = {'If-Match': element['_etag']}
            endpoint = '/'.join([object_type, object_id])
            logger.info("update_object, endpoint: %s, data: %s", endpoint, data)
            result = self.backend.patch(endpoint, data, headers)
            logger.debug("update_object, response: %s", result)
            if result['_status'] != 'OK':  # pragma: no cover, should never happen
                error = []
                if "content" in result:
                    error.append(result["content"])
                if "_issues" in result:
                    error.append(result["_issues"])
                    for issue in result["_issues"]:
                        error.append(result["_issues"][issue])
                logger.warning("update_object, error: %s", error)
                return False

            items = self.find_object(object_type, object_id)
            logger.info("update_object, updated: %s", items[0])
        except BackendException as e:  # pragma: no cover, should never happen
            logger.error("update_object, backend exception: %s", str(e))
            return False
        except ValueError:  # pragma: no cover, should never happen
            logger.warning("update_object, not found %s: %s", object_type, element)
            return False

        return True

    ##
    # Hosts
    ##
    def get_hosts(self, search=None):
        """ Get a list of all hosts. """
        if search is None:
            search = {}
        if 'embedded' not in search:
            search.update({'embedded': {'userservice_session': 1, 'user': 1}})

        try:
            logger.info("get_hosts, search: %s", search)
            items = self.find_object('host', search)
            logger.info("get_hosts, got: %d elements, %s", len(items), items)
            return items
        except ValueError:
            logger.debug("get_hosts, none found")

        return []

    def get_host(self, search):
        """ Get a host by its id (default). """

        if isinstance(search, basestring):
            search = {'max_results': 1, 'where': {'_id': search}}
        elif 'max_results' not in search:
            search.update({'max_results': 1})

        items = self.get_hosts(search=search)
        return items[0] if items else None

    def get_hosts_synthesis(self, elts=None):
        """
        Hosts synthesis by status
        """
        if elts:
            hosts = [item for item in elts if item.getType() == 'host']
        else:
            # Use internal object list ...
            hosts = [item for _id, item in Host.getCache().items()]
        logger.debug("get_hosts_synthesis, %d hosts", len(hosts))

        synthesis = dict()
        synthesis['nb_elts'] = len(hosts)
        synthesis['nb_problem'] = 0
        if hosts:
            for state in 'up', 'unreachable', 'down', 'unknown':
                synthesis['nb_' + state] = sum(
                    1 for host in hosts if host.get_status().lower() == state
                )
                synthesis['pct_' + state] = round(
                    100.0 * synthesis['nb_' + state] / synthesis['nb_elts'], 2
                )
        else:
            for state in 'up', 'unreachable', 'down', 'unknown':
                synthesis['nb_' + state] = 0
                synthesis['pct_' + state] = 0

        logger.debug("get_hosts_synthesis: %s", synthesis)
        return synthesis

    def add_host(self, data, files):
        """
        Add a host.
        Update the concerned session internal objects.
        """

        return self.add_object('host', data, files)

    ##
    # Commands
    ##
    def get_commands(self, search=None):
        """ Get a list of all commands. """
        if search is None:
            search = {}
        if 'embedded' not in search:
            search.update({'embedded': {'userservice_session': 1, 'user': 1}})

        try:
            logger.info("get_commands, search: %s", search)
            items = self.find_object('command', search)
            return items
        except ValueError:
            logger.debug("get_commands, none found")

        return []

    def get_command(self, search):
        """ Get a command by its id. """

        if isinstance(search, basestring):
            search = {'max_results': 1, 'where': {'_id': search}}
        elif 'max_results' not in search:
            search.update({'max_results': 1})

        items = self.get_commands(search=search)
        return items[0] if items else None

    def get_commands_synthesis(self, elts=None):
        """
        Documents synthesis by status
        """
        if elts:
            commands = [item for item in elts if item.getType() == 'command']
        else:
            # Use internal object list ...
            commands = [item for _id, item in Command.getCache().items()]
        logger.debug("get_commands_synthesis, %d commands", len(commands))

        synthesis = dict()
        synthesis['nb_elts'] = len(commands)
        if commands:
            for state in 'attached', 'empty', 'problem', 'unknown':
                synthesis['nb_' + state] = sum(
                    1 for command in commands if command.get_status().lower() == state
                )
                synthesis['pct_' + state] = round(
                    100.0 * synthesis['nb_' + state] / synthesis['nb_elts'], 2
                )
        else:
            for state in 'attached', 'empty', 'problem', 'unknown':
                synthesis['nb_' + state] = 0
                synthesis['pct_' + state] = 0

        logger.debug("get_commands_synthesis: %s", synthesis)
        return synthesis

    def add_command(self, data):  # pragma: no cover, not yet implemented!
        """
        Add a command.
        Update the concerned session internal objects.
        """

        return self.add_object('command', data)

    ##
    # Users
    ##
    def get_users(self, search=None):
        """ Get a list of known users """
        if not self.get_logged_user().is_administrator():
            return [self.get_logged_user()]

        try:
            logger.info("get_users, search: %s", search)
            items = self.find_object('contact', search)
            return items
        except ValueError:
            logger.debug("get_users, none found")

        return []

    def get_user(self, search):
        """
        Get a user by its id or a search pattern
        """
        if isinstance(search, basestring):
            search = {'max_results': 1, 'where': {'_id': search}}
        elif 'max_results' not in search:
            search.update({'max_results': 1})

        items = self.get_users(search=search)
        return items[0] if items else None

    def add_user(self, data):
        """ Add a user. """

        return self.add_object('user', data)

    def delete_user(self, user):
        """ Delete a user.

        Cannot delete the currently logged in user ...

        If user is a string it is assumed to be the User object id to be searched in the objects
        cache.

        :param user: User object instance
        :type user: User (or string)

        Returns True/False depending if user closed
        """
        logger.info("delete_user, request to delete the user: %s", user)

        if isinstance(user, basestring):
            user = self.get_user(user)
            if not user:
                return False

        user_id = user.get_id()
        if user_id == self.get_logged_user().get_id():
            logger.warning("delete_user, request to delete the current logged-in user: %s", user_id)
            return False

        return self.delete_object('user', user)
示例#17
0
class FrontEnd(object):
    """
    Frontend class used to communicate with alignak-backend
    """
    last_livestate_hosts = None
    last_livestate_services = None

    def __init__(self):
        """
        Initialize class object ...
        """
        self.url_endpoint_root = None
        self.backend = None

        self.connected = False
        self.initialized = False
        self.authenticated = False
        self.logged_in = None
        self.token = None

        # Backend objects which will be loaded on backend connection ...
        # ... do not need to wait for any user request to get these objects
        self.interested_in = ['contact', 'uipref']

        # Backend available objects (filled with objects received from backend)
        self.backend_available_objets = []

        # Frontend objects
        self.objects_cache = {}

        # API Data model
        self.dm_server_name = None
        self.dm_api_name = None
        self.dm_base = None
        self.dm_domains = {}

    def configure(self, endpoint):
        """
        Initialize backend connection ...
        """
        # Backend API start point
        if endpoint.endswith('/'):  # pragma: no cover - test url is complying ...
            self.url_endpoint_root = endpoint[0:-1]
        else:
            self.url_endpoint_root = endpoint

        logger.info("backend endpoint: %s", self.url_endpoint_root)
        self.backend = Backend(self.url_endpoint_root)

    def login(self, username=None, password=None, token=None, force=False):
        """
        Authenticate user credentials against backend

        :param username: user to authenticate
        :type username: string
        :param password: password
        :type password: string
        :return: user token if authenticated, else None
        :rtype: string
        """
        try:
            if self.authenticated and self.token and not force:
                return self.token

            if token:
                try:
                    # Test the backend connection
                    self.backend.token = token
                    logger.info("request backend user authentication, token: %s", token)
                    self.backend_available_objets = self.backend.get_domains()
                    if self.backend_available_objets:
                        self.authenticated = True
                        self.token = token
                        logger.info("backend user authenticated")
                except BackendException as e:
                    logger.error("frontend connection, error: %s", str(e))
                    self.authenticated = False
                    self.token = None
            else:
                self.authenticated = False
                self.token = None
                logger.info("request backend user authentication, username: %s", username)
                self.authenticated = self.backend.login(username=username, password=password)
                if self.authenticated:
                    self.token = self.backend.token

                    logger.info("backend user authenticated: %s", username)
        except BackendException as e:
            logger.error("backend login, error: %s", str(e))
            logger.debug("exception type: %s", type(e))
            logger.debug("Back trace of this kill: %s", traceback.format_exc())
            self.connected = False
            self.authenticated = self.connected
            raise e

        return self.token

    def logout(self):
        """
        Logout user from backend

        :return: True
        :rtype: boolean
        """
        self.connected = False
        self.authenticated = False
        self.token = None

        return self.backend.logout()

    def connect(self, username=None, token=None):
        """
        If backend connection is available:
        - retrieves all managed domains on the root endpoint
        - get the backend schema (models)
        - load some persistent elements (defined on init)
        - find the contact associated with the current logged in user

        :param username: authenticated user
        :type username: string
        :param token: user token
        :type token: string
        :return: true / false
        :rtype: boolean
        """
        try:
            self.connected = False
            matching_contact = False

            # Backend authentication ...
            if not self.authenticated:
                return self.connected

            # Connect the backend
            self.backend_available_objets = self.backend.get_domains()
            if self.backend_available_objets:
                self.connected = True

            if self.connected:
                # Retrieve data model from the backend
                response = requests.get('/'.join([self.url_endpoint_root, 'docs/spec.json']))
                resp = response.json()
                self.dm_server_name = resp['server_name']
                self.dm_api_name = resp['api_name']
                self.dm_base = resp['base']
                self.dm_domains = {}
                for domain_name in resp['domains']:
                    fields = resp['domains'][domain_name]["/" + domain_name]['POST']['params']
                    self.dm_domains.update({
                        domain_name: fields
                    })

                # Initialize embedded objects
                if not self.initialized:
                    self.initialize()

                # If we got contacts, try to find a contact matching our authenticated user ...
                if 'contact' in self.objects_cache:
                    for contact in self.objects_cache['contact']:
                        logger.debug(
                            "available contact: %s / %s", contact["contact_name"], contact["token"]
                        )
                        if (token and contact["token"] == token) or (
                                username and contact["contact_name"] == username):
                            matching_contact = True
                            self.logged_in = contact
                            self.token = contact["token"]
                            logger.info(
                                "found a contact matching logged in user contact: %s (%s)",
                                contact["contact_name"], contact["token"]
                            )
                    self.connected = matching_contact
                    self.authenticated = self.connected

        except BackendException as e:
            logger.error("frontend connection, error: %s", str(e))
            self.connected = False
            self.authenticated = self.connected
            raise e
        except Exception as e:  # pragma: no cover - simple protection if ever happened ...
            logger.error("frontend connection, error: %s", str(e))
            logger.debug("exception type: %s", type(e))
            logger.debug("Back trace of this kill: %s", traceback.format_exc())

        return self.connected

    def disconnect(self):
        """
        Disconnect backend

        :return: True
        :rtype: boolean
        """
        self.connected = False
        self.authenticated = False
        self.token = None
        self.logged_in = None

        self.backend.logout()

        return True

    def initialize(self):
        """
        Initialize self cached backend objects.

        Load the backend that we will keep in our cache.

        :return: true / false
        :rtype: boolean
        """
        try:
            self.initialized = False

            # Connect to backend if not yet connected ...
            if not self.connected:  # pragma: no cover - simple protection if ever happened ...
                self.connected = self.connect()

            if not self.connected:  # pragma: no cover - simple protection if ever happened ...
                return False

            # Fetch only objects type which I am interested in ...
            for object_type in self.backend_available_objets:
                t = object_type["href"]
                if t in self.interested_in:
                    logger.info(
                        "getting '%s' cached objects on %s%s",
                        object_type["title"], self.url_endpoint_root, t
                    )

                    # Get all objects of type t ...
                    items = self.get_objects(t, None, True)

                    self.objects_cache[t] = items

            self.initialized = True
        except Exception as e:  # pragma: no cover
            logger.error("FrontEnd, initialize, exception: %s", str(e))

        return self.initialized

    def get_objects(self, object_type, parameters=None, all_elements=False):
        """
        Get stored objects

        !!! NOTE !!!
        Beware of the all_elements=True parameter because the backend client method fetches all
        the elements and the get_objects is not able anymore to send the _meta information !

        :param object_type: object type (eg. host, contact, ...)
        :type object_type: str
        :param parameters: list of parameters for the backend API
        :type parameters: list
        :param all_elements: get all elements (True) or apply default pagination
        :type all_elements: bool
        :return: list of properties when query item | list of items when get many items
        :rtype: list
        """
        try:
            items = []

            logger.info(
                "get_objects, type: %s, parameters: %s / %d (%s)",
                object_type, parameters, all_elements, self.token
            )

            # If requested objects are stored locally ...
            if object_type in self.objects_cache:
                logger.debug("get_objects, returns local store objects")
                return {'_items': self.objects_cache[object_type]}

            # Request objects from the backend ...
            if all_elements:
                items = self.backend.get_all(object_type, parameters)
            else:
                items = self.backend.get(object_type, parameters)
            # logger.debug("get_objects, type: %s, items: %s", object_type, items)

            # Should be handled in the try / except ... but exception is not always raised!
            if '_error' in items:  # pragma: no cover - need specific backend tests
                error = items['_error']
                logger.error(
                    "backend get: %s, %s",
                    error['code'], error['message']
                )
                items = []

        except Exception as e:  # pragma: no cover - need specific backend tests
            logger.error("get_objects, exception: %s", str(e))

        return items

    def set_user_preferences(self, user, prefs_type, parameters):
        """
        Set user's preferences

        An exception is raised if an error occurs, else returns the backend response

        :param user: username
        :type user: str
        :param prefs_type: preference type
        :type prefs_type: str
        :param parameters: list of parameters for the backend API
        :type parameters: list
        :return: server's response
        :rtype: dict
        """
        try:
            response = None

            logger.debug(
                "set_user_preferences, type: %s, for: %s, parameters: %s",
                prefs_type, user, parameters
            )

            # Still existing ...
            items = self.backend.get_all(
                'uipref',
                params={'where': '{"type":"%s", "user": "******"}' % (prefs_type, user)}
            )
            if items:
                items = items[0]
                logger.info(
                    "set_user_preferences, update exising record: %s / %s (%s)",
                    prefs_type, user, items['_id']
                )
                # Update existing record ...
                headers = {'If-Match': items['_etag']}
                data = {
                    "user": user,
                    "type": prefs_type,
                    "data": parameters
                }
                response = self.backend.patch(
                    '/'.join(['uipref', items['_id']]),
                    data=data, headers=headers, inception=True
                )
            else:
                # Create new record ...
                logger.info(
                    "set_user_preferences, create new record: %s / %s",
                    prefs_type, user
                )
                data = {
                    "user": user,
                    "type": prefs_type,
                    "data": parameters
                }
                response = self.backend.post('uipref', data=data)
            logger.debug("set_user_preferences, response: %s", response)

        except Exception as e:  # pragma: no cover - need specific backend tests
            logger.error("set_user_preferences, exception: %s", str(e))
            raise e

        return response

    def get_user_preferences(self, user, prefs_type):
        """
        Get user's preferences

        If the data are not found, returns None else return found data.

        :param user: username
        :type user: str
        :param prefs_type: preference type
        :type prefs_type: str
        :return: found data, or None
        :rtype: dict
        """
        try:
            logger.debug("get_user_preferences, type: %s, for: %s", prefs_type, user)

            # Still existing ...
            items = self.backend.get_all(
                'uipref',
                params={'where': '{"type":"%s", "user": "******"}' % (prefs_type, user)}
            )
            if items:
                logger.debug("get_user_preferences, found: %s", items[0])
                return items[0]

        except Exception as e:  # pragma: no cover - need specific backend tests
            logger.error("get_user_preferences, exception: %s", str(e))
            raise e

        return None  # pragma: no cover - need specific backend tests

    def get_ui_data_model(self, element_type):
        """ Get the data model for an element type

            If the data model specifies that the element is managed in the UI,
            all the fields for this element are provided

            :param element_type: element type
            :type element_type: str
            :return: list of fields name/title
            :rtype: list
        """
        logger.debug("get_ui_data_model, element type: %s", element_type)
        logger.debug("get_ui_data_model, domains: %s", self.dm_domains)

        fields = []
        if element_type in self.dm_domains:
            for model in self.dm_domains[element_type]:
                logger.debug("get_ui_data_model, model: %s", model["name"])
                # element is considered for the UI
                if 'ui' in model["name"]:
                    fields = self.dm_domains[element_type]
                    break
        return fields

    def get_livestate(self, parameters=None, all_elements=True):
        """ Get livestate for hosts and services

            :param parameters: backend request parameters
            :type parameters: dic
            :param all_elements: get all elements (True) or apply default pagination
            :type all_elements: bool
            :return: list of hosts/services live states
            :rtype: list
        """
        return self.get_objects('livestate', parameters, all_elements=all_elements)

    def get_livestate_hosts(self, parameters=None, all_elements=True):
        """ Get livestate for hosts

            Elements in the livestat which service_description is null (eg. hosts)

            :param parameters: backend request parameters
            :type parameters: dic
            :param all_elements: get all elements (True) or apply default pagination
            :type all_elements: bool
            :return: list of hosts live states
            :rtype: list
        """
        if not parameters:
            parameters = {}
        parameters.update({'where': '{"service_description": null}'})
        return self.get_objects('livestate', parameters, all_elements=all_elements)

    def get_livestate_services(self, parameters=None, all_elements=True):
        """ Get livestate for services

            Elements in the livestat which service_description is not null (eg. services)

            :param parameters: backend request parameters
            :type parameters: dic
            :param all_elements: get all elements (True) or apply default pagination
            :type all_elements: bool
            :return: list of services live states
            :rtype: list
        """
        if not parameters:
            parameters = {}
        parameters.update({'where': '{"service_description": {"$ne": null}}'})
        return self.get_objects('livestate', parameters, all_elements=all_elements)

    def get_livesynthesis(self, parameters=None, all_elements=True):
        """ Get livestate synthesis for hosts and services

            Example backend response :
            {
                "hosts_total": 12,
                "hosts_business_impact": 0,
                "hosts_acknowledged": 0,
                "hosts_in_downtime": 0,
                "hosts_flapping": 0,
                "hosts_up_hard": 0,
                "hosts_up_soft": 0,
                "hosts_unreachable_hard": 0,
                "hosts_unreachable_soft": 0,
                "hosts_down_hard": 0,
                "hosts_down_soft": 0,

                "services_total": 245,
                "services_business_impact": 0,
                "services_acknowledged": 0,
                "services_in_downtime": 0,
                "services_flapping": 0,
                "services_ok_hard": 0,
                "services_ok_soft": 0,
                "services_warning_hard": 0,
                "services_warning_soft": 0,
                "services_critical_hard": 0,
                "services_critical_soft": 0,
                "services_unknown_soft": 0,
                "services_unknown_hard": 0,
                "_created": "Thu, 01 Jan 1970 00:00:00 GMT",
                "_updated": "Sat, 10 Oct 2015 09:08:59 GMT",
                "_id": "5618d5abf9e3852e3444a5ee",
                "_etag": "edce4629fff2412ab7257216bb66c54795baada4"
                "_links": {
                    "self": {
                        "href": "livesynthesis/5618d5abf9e3852e3444a5ee",
                        "title": "Livesynthesi"
                    }
                },
            }

            Returns an hosts_synthesis dictionary containing:
            - number of elements
            - business impact
            - count for each state (hard and soft)
            - percentage for each state (hard and soft)
            - number of problems (down and unreachable, only hard state)
            - percentage of problems

            Returns a services_synthesis dictionary containing:
            - number of elements
            - business impact
            - count for each state (hard and soft)
            - percentage for each state (hard and soft)
            - number of problems (down and unreachable, only hard state)
            - percentage of problems

            :return: hosts and services live state synthesis in a dictionary
            :rtype: dict
        """
        ls = self.get_objects('livesynthesis', parameters, all_elements=all_elements)
        if not ls:
            return None

        ls = ls[0]

        # Services synthesis
        hosts_synthesis = {
            'nb_elts': ls["hosts_total"],
            'business_impact': ls["hosts_business_impact"],
        }
        for state in 'up', 'down', 'unreachable':
            hosts_synthesis.update({
                "nb_" + state: ls["hosts_%s_hard" % state] + ls["hosts_%s_soft" % state]
            })
        for state in 'acknowledged', 'in_downtime', 'flapping':
            hosts_synthesis.update({
                "nb_" + state: ls["hosts_%s" % state]
            })
        hosts_synthesis.update({
            "nb_problems": ls["hosts_down_hard"] + ls["hosts_unreachable_hard"]
        })
        for state in 'up', 'down', 'unreachable':
            hosts_synthesis.update({
                "pct_" + state: round(
                    100.0 * hosts_synthesis['nb_' + state] / hosts_synthesis['nb_elts'], 2
                ) if hosts_synthesis['nb_elts'] else 0.0
            })
        for state in 'acknowledged', 'in_downtime', 'flapping', 'problems':
            hosts_synthesis.update({
                "pct_" + state: round(
                    100.0 * hosts_synthesis['nb_' + state] / hosts_synthesis['nb_elts'], 2
                ) if hosts_synthesis['nb_elts'] else 0.0
            })

        # Services synthesis
        services_synthesis = {
            'nb_elts': ls["services_total"],
            'business_impact': ls["services_business_impact"],
        }
        for state in 'ok', 'warning', 'critical', 'unknown':
            services_synthesis.update({
                "nb_" + state: ls["services_%s_hard" % state] + ls["services_%s_soft" % state]
            })
        for state in 'acknowledged', 'in_downtime', 'flapping':
            services_synthesis.update({
                "nb_" + state: ls["services_%s" % state]
            })
        services_synthesis.update({
            "nb_problems": ls["services_warning_hard"] + ls["services_critical_hard"]
        })
        for state in 'ok', 'warning', 'critical', 'unknown':
            services_synthesis.update({
                "pct_" + state: round(
                    100.0 * services_synthesis['nb_' + state] / services_synthesis['nb_elts'], 2
                ) if services_synthesis['nb_elts'] else 0.0
            })
        for state in 'acknowledged', 'in_downtime', 'flapping', 'problems':
            services_synthesis.update({
                "pct_" + state: round(
                    100.0 * services_synthesis['nb_' + state] / services_synthesis['nb_elts'], 2
                ) if services_synthesis['nb_elts'] else 0.0
            })

        synthesis = {
            'hosts_synthesis': hosts_synthesis,
            'services_synthesis': services_synthesis
        }
        return synthesis

    def get_hosts_synthesis(self):
        """
        Deprecated function !

        Add an API endpoint /hosts_synthesis
        --------------------------------------------------------------------------------------------
        Returns an hosts live state synthesis containing:
            {'nb_down': 4, 'pct_up': 66.67, 'pct_down': 33.33, 'nb_unreachable': 0,
            'nb_unknown': 0, 'pct_problems': 33.33, 'nb_downtime': 0, 'nb_problems': 4,
            'bi': 3, 'pct_unknown': 0.0, 'nb_ack': 0, 'nb_elts': 12, 'nb_up': 8,
            'nb_pending': 0, 'pct_ack': 0.0, 'pct_pending': 0.0, 'pct_downtime': 0.0,
            'pct_unreachable': 0.0}

        Returns none if no hosts are available

        :return: hosts live state synthesis
        :rtype: dict
        """
        parameters = {"embedded": '{"host_name":1}'}
        hosts = self.get_livestate_hosts(parameters=parameters)
        if not hosts:
            return None

        h = dict()
        h['nb_elts'] = len(hosts)
        h['bi'] = max(int(i['host_name']['business_impact'])
                      for i in hosts if 'business_impact' in i['host_name'])
        for state in 'up', 'down', 'unreachable', 'pending':
            h[state] = [i for i in hosts if i['state'] == state.upper()]
        h['unknown'] = [i for i in hosts if i['state'].lower()
                        not in ['up', 'down', 'unreachable', 'pending']]
        h['ack'] = [i for i in hosts if i['state'] not in ['UP', 'PENDING'] and
                    ('acknowledged' in i and i['acknowledged'])]
        h['downtime'] = [i for i in hosts if ('in_scheduled_downtime' in i and
                                              i['in_scheduled_downtime'])]
        for state in 'up', 'down', 'unreachable', 'pending', 'unknown', 'ack', 'downtime':
            h['nb_' + state] = len(h[state])
            h['pct_' + state] = 0
            if hosts:
                h['pct_' + state] = round(100.0 * h['nb_' + state] / h['nb_elts'], 2)
            del h[state]
        h['nb_problems'] = h['nb_down'] + h['nb_unreachable'] + h['nb_unknown']
        h['pct_problems'] = 0
        if hosts:
            h['pct_problems'] = round(100.0 * h['nb_problems'] / h['nb_elts'], 2)

        logger.info("get_hosts_synthesis: %s, %s", type(h), h)
        return h

    def get_services_synthesis(self):
        """
        Deprecated function !

        Add an API endpoint /services_synthesis
        --------------------------------------------------------------------------------------------
        Returns a services live state synthesis containing:
            {'nb_critical': 4, 'pct_ok': 66.67, 'pct_critical': 33.33, 'nb_warning': 0,
            'nb_unknown': 0, 'pct_problems': 33.33, 'nb_downtime': 0, 'nb_problems': 4,
            'bi': 3, 'pct_unknown': 0.0, 'nb_ack': 0, 'nb_elts': 12, 'nb_ok': 8,
            'nb_pending': 0, 'pct_ack': 0.0, 'pct_pending': 0.0, 'pct_downtime': 0.0,
            'pct_warning': 0.0}

        Returns none if no services are available

        :return: services live state synthesis
        :rtype: dict
        """
        parameters = {"embedded": '{"service_description":1}'}
        services = self.get_livestate_services(parameters=parameters)
        if not services:
            return None

        s = dict()
        s['nb_elts'] = len(services)
        s['bi'] = max(int(i['service_description']['business_impact'])
                      for i in services if 'business_impact' in i['service_description'])
        for state in 'ok', 'critical', 'warning', 'pending':
            s[state] = [i for i in services if i['state'] == state.upper()]
        s['unknown'] = [i for i in services if i['state'].lower()
                        not in ['ok', 'critical', 'warning', 'pending']]
        s['ack'] = [i for i in services if i['state']
                    not in ['OK', 'PENDING'] and i['acknowledged']]
        s['downtime'] = [i for i in services if i['state']
                         not in ['up', 'pending'] and i['acknowledged']]
        for state in 'ok', 'critical', 'warning', 'unknown', 'pending', 'ack', 'downtime':
            s['nb_' + state] = len(s[state])
            s['pct_' + state] = 0
            if services:
                s['pct_' + state] = round(100.0 * s['nb_' + state] / s['nb_elts'], 2)
            del s[state]
        s['nb_problems'] = s['nb_warning'] + s['nb_critical'] + s['nb_unknown']
        s['pct_problems'] = 0
        if services:
            s['pct_problems'] = round(100.0 * s['nb_problems'] / s['nb_elts'], 2)

        logger.info("get_services_synthesis: %s", s)
        return s
示例#18
0
class AlignakBackendBroker(BaseModule):
    """ This class is used to send logs and livestate to alignak-backend
    """
    def __init__(self, mod_conf):
        """Module initialization

        mod_conf is a dictionary that contains:
        - all the variables declared in the module configuration file
        - a 'properties' value that is the module properties as defined globally in this file

        :param mod_conf: module configuration file as a dictionary
        """
        BaseModule.__init__(self, mod_conf)

        # pylint: disable=global-statement
        global logger
        logger = logging.getLogger('alignak.module.%s' % self.alias)
        logger.setLevel(getattr(mod_conf, 'log_level', logging.INFO))

        logger.debug("inner properties: %s", self.__dict__)
        logger.debug("received configuration: %s", mod_conf.__dict__)

        self.client_processes = int(getattr(mod_conf, 'client_processes', 1))
        logger.info("Number of processes used by backend client: %s",
                    self.client_processes)

        self.default_realm = None

        logger.info("StatsD configuration: %s:%s, prefix: %s, enabled: %s",
                    getattr(mod_conf, 'statsd_host', 'localhost'),
                    int(getattr(mod_conf, 'statsd_port', '8125')),
                    getattr(mod_conf, 'statsd_prefix', 'alignak'),
                    (getattr(mod_conf, 'statsd_enabled', '0') != '0'))
        self.statsmgr = Stats()
        self.statsmgr.register(
            self.alias,
            'module',
            statsd_host=getattr(mod_conf, 'statsd_host', 'localhost'),
            statsd_port=int(getattr(mod_conf, 'statsd_port', '8125')),
            statsd_prefix=getattr(mod_conf, 'statsd_prefix', 'alignak'),
            statsd_enabled=(getattr(mod_conf, 'statsd_enabled', '0') != '0'))

        self.url = getattr(mod_conf, 'api_url', 'http://localhost:5000')
        logger.info("Alignak backend endpoint: %s", self.url)
        self.backend_connected = False
        self.backend_connection_retry_planned = 0
        try:
            self.backend_connection_retry_delay = int(
                getattr(mod_conf, 'backend_connection_retry_delay', '10'))
        except ValueError:
            self.backend_connection_retry_delay = 10
        logger.info("backend connection retry delay: %.2f seconds",
                    self.backend_connection_retry_delay)

        self.backend_errors_count = 0
        self.backend_username = getattr(mod_conf, 'username', '')
        self.backend_password = getattr(mod_conf, 'password', '')
        self.backend_generate = getattr(mod_conf, 'allowgeneratetoken', False)

        self.backend_count = int(getattr(mod_conf, 'backend_count', '50'))
        logger.info("backend pagination count: %d items", self.backend_count)

        self.backend_token = getattr(mod_conf, 'token', '')
        self.backend = Backend(self.url, self.client_processes)

        self.manage_update_program_status = getattr(mod_conf,
                                                    'update_program_status',
                                                    '0') == '1'
        logger.info("manage update_program_status broks: %s",
                    self.manage_update_program_status)

        # Log in to the backend
        self.logged_in = False
        self.backend_connected = self.backend_connection()

        # Get the default realm
        self.default_realm = self.get_default_realm()

        self.ref_live = {'host': {}, 'service': {}, 'user': {}}
        self.mapping = {'host': {}, 'service': {}, 'user': {}}

        # Objects reference
        self.load_protect_delay = int(
            getattr(mod_conf, 'load_protect_delay', '300'))
        self.last_load = 0

        # Backend to be posted data
        self.logcheckresults = []

    # Common functions
    def do_loop_turn(self):
        """This function is called/used when you need a module with
        a loop function (and use the parameter 'external': True)

        Note: We are obliged to define this method (even if not called!) because
        it is an abstract function in the base class
        """
        logger.info("In loop")
        time.sleep(1)

    def raise_backend_alert(self, errors_count=10):
        """Raise a backend alert

        :return: True if the backend is not connected and the error count
        is greater than a defined threshold
        """
        logger.debug(
            "Check backend connection, connected: %s, errors count: %d",
            self.backend_connected, self.backend_errors_count)
        if not self.backend_connected and self.backend_errors_count >= errors_count:
            return True

        return False

    def backend_connection(self):
        """Backend connection to check live state update is allowed

        :return: True/False
        """
        if self.backend_login():
            self.get_default_realm()

            try:
                start = time.time()
                params = {'where': '{"token":"%s"}' % self.backend.token}
                users = self.backend.get('user', params)
                self.statsmgr.counter('backend-get.user', 1)
                self.statsmgr.timer('backend-get-time.user',
                                    time.time() - start)
            except BackendException as exp:
                logger.warning(
                    "Error on backend when retrieving user information: %s",
                    exp)
            else:
                try:
                    for item in users['_items']:
                        self.logged_in = item['can_update_livestate']
                    return self.logged_in
                except Exception as exp:
                    logger.error(
                        "Can't get the user information in the backend response: %s",
                        exp)

        logger.error("Configured user account is not allowed for this module")
        return False

    def backend_login(self):
        """Log in to the backend

        :return: bool
        """
        generate = 'enabled'
        if not self.backend_generate:
            generate = 'disabled'

        if self.backend_token:
            # We have a token, don't ask for a new one
            self.backend.token = self.backend_token
            connected = True  # Not really yet, but assume yes
        else:
            if not self.backend_username or not self.backend_password:
                logger.error(
                    "No user or password supplied, and no default token defined. "
                    "Can't connect to backend")
                connected = False
            else:
                try:
                    start = time.time()
                    connected = self.backend.login(self.backend_username,
                                                   self.backend_password,
                                                   generate)
                    self.statsmgr.counter('backend-login', 1)
                    self.statsmgr.timer('backend-login-time',
                                        time.time() - start)
                except BackendException as exp:
                    logger.error("Error on backend login: %s", exp)
                    connected = False

        return connected

    def get_default_realm(self):
        """
        Retrieves the default top level realm for the connected user
        :return: str or None
        """
        default_realm = None

        if self.backend_connected:
            try:
                start = time.time()
                result = self.backend.get('/realm', {
                    'max_results': 1,
                    'sort': '_level'
                })
                self.statsmgr.counter('backend-get.realm', 1)
                self.statsmgr.timer('backend-get-time.realm',
                                    time.time() - start)
            except BackendException as exp:
                logger.warning(
                    "Error on backend when retrieving default realm: %s", exp)
            else:
                try:
                    default_realm = result['_items'][0]['_id']
                except Exception as exp:
                    logger.error(
                        "Can't get the default realm in the backend response: %s",
                        exp)

        return default_realm

    def get_refs(self):
        """
        Get the _id in the backend for hosts, services and users

        :return: None
        """
        start = time.time()
        now = int(time.time())

        logger.info("Got a new configuration, reloading objects...")

        # Get managed inter-process dicts
        host_mapping = self.mapping['host']
        serv_mapping = self.mapping['service']
        user_mapping = self.mapping['user']
        host_ref_live = self.ref_live['host']
        serv_ref_live = self.ref_live['service']
        user_ref_live = self.ref_live['user']

        if now - self.last_load > self.load_protect_delay:
            logger.info("Got a new configuration, reloading objects...")
            # Updating hosts
            hosts = {}
            params = {
                'projection':
                '{"name":1,"ls_state":1,"ls_state_type":1,"_realm":1}',
                'max_results': self.backend_count,
                'where': '{"_is_template":false}'
            }
            content = self.backend.get_all('host', params)
            self.statsmgr.counter('backend-getall.host', 1)
            for item in content['_items']:
                host_mapping[item['name']] = item['_id']

                host_ref_live[item['_id']] = {
                    '_id': item['_id'],
                    '_etag': item['_etag'],
                    '_realm': item['_realm'],
                    'initial_state': item['ls_state'],
                    'initial_state_type': item['ls_state_type']
                }
                hosts[item['_id']] = item['name']
            logger.info("- hosts references reloaded")

            # Updating services
            params = {
                'projection':
                '{"host":1,"name":1,"ls_state":1,"ls_state_type":1,"_realm":1}',
                'max_results': self.backend_count,
                'where': '{"_is_template":false}'
            }
            content = self.backend.get_all('service', params)
            self.statsmgr.counter('backend-getall.service', 1)
            for item in content['_items']:
                try:
                    serv_mapping['__'.join([hosts[item['host']],
                                            item['name']])] = item['_id']

                    serv_ref_live[item['_id']] = {
                        '_id': item['_id'],
                        '_etag': item['_etag'],
                        '_realm': item['_realm'],
                        'initial_state': item['ls_state'],
                        'initial_state_type': item['ls_state_type']
                    }
                except KeyError:
                    logger.warning("Got a service for an unknown host")
            logger.info("- services references reloaded")

            # Updating users
            params = {
                'projection': '{"name":1,"_realm":1}',
                'max_results': self.backend_count,
                'where': '{"_is_template":false}'
            }
            content = self.backend.get_all('user', params)
            self.statsmgr.counter('backend-getall.user', 1)
            for item in content['_items']:
                user_mapping[item['name']] = item['_id']

                user_ref_live[item['_id']] = {
                    '_id': item['_id'],
                    '_etag': item['_etag'],
                    '_realm': item['_realm']
                }
            logger.info("- users references reloaded")

            self.last_load = now
        else:
            logger.warning(
                "- references not reloaded. Last reload is too recent; "
                "set the 'load_protect_delay' parameter accordingly.")

        # Propagate changes in the inter-process dicts
        self.mapping['host'] = host_mapping
        self.mapping['service'] = serv_mapping
        self.mapping['user'] = user_mapping
        self.ref_live['host'] = host_ref_live
        self.ref_live['service'] = serv_ref_live
        self.ref_live['user'] = user_ref_live

        end = time.time()
        self.statsmgr.timer('backend-getall.time', end - start)

        return True

    def update_next_check(self, data, obj_type):
        """Update livestate host and service next check timestamp

        {'instance_id': u'475dc864674943b4aa4cbc966f7cc737', u'service_description': u'nsca_disk',
        u'next_chk': 0, u'in_checking': True, u'host_name': u'ek3022fdj-00011'}

        :param data: dictionary of data from scheduler
        :type data: dict
        :param obj_type: type of data (host | service)
        :type obj_type: str
        :return: False if backend update problem
        :rtype: bool
        """
        logger.debug("Update next check: %s, %s", obj_type, data)

        if obj_type == 'host':
            if data['host_name'] in self.mapping['host']:
                # Received data for an host:
                data_to_update = {'ls_next_check': data['next_chk']}

                # Update live state
                return self.send_to_backend('livestate_host',
                                            data['host_name'], data_to_update)
        elif obj_type == 'service':
            service_name = '__'.join(
                [data['host_name'], data['service_description']])
            if service_name in self.mapping['service']:
                # Received data for a service:
                data_to_update = {'ls_next_check': data['next_chk']}

                # Update live state
                return self.send_to_backend('livestate_service', service_name,
                                            data_to_update)

        return False

    def check_result(self, data):
        """
        Got a check result for an host/service

        :param data: brok data got from scheduler
        :type data: dict
        :return: False if any error when posting to the backend
        """
        logger.debug("Manage a check result: %s", data)

        # Received data for an host or service
        # Obliged to set an _realm... despite it is unuseful.
        posted_data = {
            '_realm': self.default_realm,
            'state': data['state'],
            'state_type': data['state_type'],
            'state_id': data['state_id'],
            'passive_check':
            data['passive_check'] if 'passive_check' in data else False,
            'acknowledged': data['problem_has_been_acknowledged'],
            'acknowledgement_type': data['acknowledgement_type'],
            'downtimed': data['in_scheduled_downtime'],
            'last_check': data['last_chk'],
            'last_state': data['last_state'],
            'last_state_id': data['last_state_id'],
            'last_state_type': data['last_state_type'],
            'output': data['output'],
            'long_output': data['long_output'],
            'perf_data': data['perf_data'],
            'latency': data['latency'],
            'execution_time': data['execution_time'],
            'current_attempt': data['attempt'],

            # 'state_changed': data['state_changed'],
            'last_state_changed': data['last_state_change'],
            'last_hard_state_changed': data['last_hard_state_change'],
        }
        if 'service_description' in data:
            posted_data.update({
                'host_name': data['host_name'],
                'service_name': data['service_description'],
                # Last time in the corresponding state
                'last_time_0': data['last_time_ok'],
                'last_time_1': data['last_time_warning'],
                'last_time_2': data['last_time_critical'],
                'last_time_3': data['last_time_unknown'],
                'last_time_4': data['last_time_unreachable']
            })
        else:
            posted_data.update({
                'host_name': data['host_name'],
                # Last time in the corresponding state
                'last_time_0': data['last_time_up'],
                'last_time_1': data['last_time_down'],
                'last_time_2': 0,
                'last_time_3': 0,
                'last_time_4': data['last_time_unreachable']
            })

        # Not managed currently
        # if 'initial_state' in self.ref_live['host'][h_id]:
        #     data_to_update['ls_last_state'] = \
        #         self.ref_live['host'][h_id]['initial_state']
        #     data_to_update['ls_last_state_type'] = \
        #         self.ref_live['host'][h_id]['initial_state_type']
        #     del self.ref_live['host'][h_id]['initial_state']
        #     del self.ref_live['host'][h_id]['initial_state_type']
        self.logcheckresults.append(posted_data)

    def update_status(self, brok):
        # pylint: disable=too-many-locals
        """We manage the status change for a backend host/service/contact

        :param brok: the brok
        :type brok:
        :return: None
        """
        if 'contact_name' in brok.data:
            contact_name = brok.data['contact_name']
            if brok.data['contact_name'] not in self.mapping['user']:
                logger.warning("Got a brok for an unknown user: '******'",
                               contact_name)
                return None
            endpoint = 'user'
            name = contact_name
            item_id = self.mapping['user'][name]
        else:
            host_name = brok.data['host_name']
            if brok.data['host_name'] not in self.mapping['host']:
                logger.warning("Got a brok for an unknown host: '%s'",
                               host_name)
                return None
            endpoint = 'host'
            name = host_name
            item_id = self.mapping['host'][name]
            if 'service_description' in brok.data:
                service_name = '__'.join(
                    [host_name, brok.data['service_description']])
                endpoint = 'service'
                name = service_name
                item_id = self.mapping['service'][name]
                if service_name not in self.mapping['service']:
                    logger.warning("Got a brok for an unknown service: '%s'",
                                   service_name)
                    return None

        # Sort brok properties
        sorted_brok_properties = sorted(brok.data)
        logger.debug("Update status %s: %s", endpoint, sorted(brok.data))

        # Search the concerned element
        start = time.time()
        self.statsmgr.counter('backend-get.%s' % endpoint, 1)
        item = self.backend.get(endpoint + '/' + item_id)
        self.statsmgr.timer('backend-get-time.%s' % endpoint,
                            time.time() - start)
        logger.debug("Found %s: %s", endpoint, sorted(item))

        differences = {}
        for key in sorted_brok_properties:
            value = brok.data[key]
            # Filter livestate keys...
            if "ls_%s" % key in item:
                logger.debug("Filtered live state: %s", key)
                continue

            # Filter noisy keys...
            if key in ["display_name", "tags", "notificationways"]:
                logger.debug("Filtered noisy key: %s", key)
                continue

            # Filter linked objects...
            if key in [
                    'parents', 'parent_dependencies', 'check_command',
                    'event_handler', 'snapshot_command', 'check_period',
                    'maintenance_period', 'snapshot_period',
                    'notification_period', 'host_notification_period',
                    'service_notification_period',
                    'host_notification_commands',
                    'service_notification_commands', 'contacts',
                    'contact_groups', 'hostgroups', 'checkmodulations'
            ]:
                logger.debug("Filtered linked object: %s", key)
                continue

            if key not in item:
                logger.debug("Not existing: %s", key)
                continue

            if item[key] != value:
                if isinstance(value, bool):
                    logger.debug("Different (%s): '%s' != '%s'!", key,
                                 item[key], value)
                    differences.update({key: value})
                elif not item[key] and not value:
                    logger.info(
                        "Different but empty fields (%s): '%s' != "
                        "'%s' (brok), types: %s / %s", key, item[key], value,
                        type(item[key]), type(value))
                else:
                    logger.debug("Different (%s): '%s' != '%s'!", key,
                                 item[key], value)
                    differences.update({key: value})
            else:
                logger.debug("Identical (%s): '%s'.", key, value)

        update = False
        if differences:
            logger.info("%s / %s, some modifications exist: %s.", endpoint,
                        item['name'], differences)

            headers = {
                'Content-Type': 'application/json',
                'If-Match': item['_etag']
            }
            try:
                start = time.time()
                self.statsmgr.counter('backend-patch.%s' % endpoint, 1)
                response = self.backend.patch(
                    '%s/%s' % (endpoint, item['_id']), differences, headers,
                    True)
                self.statsmgr.counter('backend-patch.%s' % endpoint, 1)
                self.statsmgr.timer('backend-patch-time.%s' % endpoint,
                                    time.time() - start)
                if response[
                        '_status'] == 'ERR':  # pragma: no cover - should not happen
                    logger.warning("Update %s: %s failed, errors: %s.",
                                   endpoint, name, response['_issues'])
                else:
                    update = True
                    logger.info("Updated %s: %s.", endpoint, name)

                if endpoint == 'host':
                    self.ref_live['host'][self.mapping['host'][host_name]]['_etag'] = \
                        response['_etag']
                elif endpoint == 'service':
                    self.ref_live['service'][self.mapping['service'][service_name]]['_etag'] = \
                        response['_etag']
            except BackendException as exp:  # pragma: no cover - should not happen
                logger.error("Update %s '%s' failed", endpoint, name)
                logger.error("Data: %s", differences)
                if exp.code == 404:
                    logger.error('Seems the %s %s deleted in the Backend',
                                 endpoint, name)
                elif exp.code == 412:
                    logger.error('Seems the %s %s was modified in the Backend',
                                 endpoint, name)
                else:
                    logger.exception("Exception: %s", exp)
                    self.backend_connected = False
                    self.backend_connection_retry_planned = \
                        int(time.time()) + self.backend_connection_retry_delay

        return update

    def update_program_status(self, brok):
        """Manage the whole program status change

        `program_status` brok is raised on program start whereas `update_program_status` brok
        is raised on every scheduler loop.

        `program_status` and `update_program_status` broks may contain:
        {
            # Some general information
            u'alignak_name': u'arbiter-master',
            u'instance_id': u'176064a1b30741d39452415097807ab0',
            u'instance_name': u'scheduler-master',

            # Some running information
            u'program_start': 1493969754,
            u'daemon_mode': 1,
            u'pid': 68989,
            u'last_alive': 1493970641,
            u'last_command_check': 1493970641,
            u'last_log_rotation': 1493970641,
            u'is_running': 1,

            # Some configuration parameters
            u'process_performance_data': True,
            u'passive_service_checks_enabled': True,
            u'event_handlers_enabled': True,
            u'command_file': u'',
            u'global_host_event_handler': None,
            u'interval_length': 60,
            u'modified_host_attributes': 0,
            u'check_external_commands': True,
            u'modified_service_attributes': 0,
            u'passive_host_checks_enabled': True,
            u'global_service_event_handler': None,
            u'notifications_enabled': True,
            u'check_service_freshness': True,
            u'check_host_freshness': True,
            u'flap_detection_enabled': True,
            u'active_service_checks_enabled': True,
            u'active_host_checks_enabled': True
        }

        :param brok: the brok
        :type brok:
        :return: None
        """
        if 'alignak_name' not in brok.data:
            logger.warning(
                "Missing alignak_name in the brok data, "
                "the program status cannot be updated. "
                "Your Alignak framework version is too old to support this feature."
            )
            return
        if not self.default_realm:
            logger.warning("Missing Alignak backend default realm, "
                           "the program status cannot be updated. "
                           "Your Alignak backend is in a very bad state!")
            return

        # Set event handlers as strings - simple protectection
        if 'global_host_event_handler' in brok.data and \
                not isinstance(brok.data['global_host_event_handler'], str):
            brok.data['global_host_event_handler'] = str(
                brok.data['global_host_event_handler'])

        if 'global_service_event_handler' in brok.data and \
                not isinstance(brok.data['global_service_event_handler'], str):
            brok.data['global_service_event_handler'] = \
                str(brok.data['global_service_event_handler'])

        name = brok.data.pop('alignak_name')
        brok.data['name'] = name
        brok.data['_realm'] = self.default_realm

        params = {'sort': '_id', 'where': '{"name": "%s"}' % name}
        start = time.time()
        all_alignak = self.backend.get_all('alignak', params)
        self.statsmgr.counter('backend-getall.alignak', 1)
        self.statsmgr.timer('backend-getall-time.alignak', time.time() - start)
        logger.debug("Got %d Alignak configurations for %s",
                     len(all_alignak['_items']), name)

        headers = {'Content-Type': 'application/json'}
        if not all_alignak['_items']:
            try:
                start = time.time()
                self.statsmgr.counter('backend-post.alignak', 1)
                response = self.backend.post('alignak', brok.data)
                self.statsmgr.timer('backend-post-time.alignak',
                                    time.time() - start)
                if response[
                        '_status'] == 'ERR':  # pragma: no cover - should not happen
                    logger.warning("Create alignak: %s failed, errors: %s.",
                                   name, response['_issues'])
                else:
                    logger.info("Created alignak: %s.", name)
            except BackendException as exp:  # pragma: no cover - should not happen
                logger.error("Create alignak '%s' failed", name)
                logger.error("Data: %s", brok.data)
                logger.exception("Exception: %s", exp)
                self.backend_connected = False
                self.backend_connection_retry_planned = \
                    int(time.time()) + self.backend_connection_retry_delay

        else:
            item = all_alignak['_items'][0]
            for key in item:
                if key not in brok.data:
                    continue
                if item[key] == brok.data[key]:
                    brok.data.pop(key)
                    continue
                logger.debug("- updating: %s = %s", key, brok.data[key])

            if not brok.data:
                logger.debug("Nothing to update")
                return

            headers['If-Match'] = item['_etag']
            try:
                start = time.time()
                self.statsmgr.counter('backend-patch.alignak', 1)
                response = self.backend.patch('alignak/%s' % (item['_id']),
                                              brok.data, headers, True)
                self.statsmgr.timer('backend-patch-time.alignak',
                                    time.time() - start)
                if response[
                        '_status'] == 'ERR':  # pragma: no cover - should not happen
                    logger.warning("Update alignak: %s failed, errors: %s.",
                                   name, response['_issues'])
                else:
                    logger.debug("Updated alignak: %s. %s", name, response)
            except BackendException as exp:  # pragma: no cover - should not happen
                logger.error("Update alignak '%s' failed", name)
                logger.error("Data: %s", brok.data)
                if exp.code == 404:
                    logger.error('Seems the alignak %s deleted in the Backend',
                                 name)
                elif exp.code == 412:
                    logger.error(
                        'Seems the alignak %s was modified in the Backend',
                        name)
                else:
                    logger.exception("Exception: %s / %s", exp, exp.response)
                    self.backend_connected = False
                    self.backend_connection_retry_planned = \
                        int(time.time()) + self.backend_connection_retry_delay

    def update_actions(self, brok):
        """We manage the acknowledge and downtime broks

        :param brok: the brok
        :type brok:
        :return: None
        """
        host_name = brok.data['host']
        if host_name not in self.mapping['host']:
            logger.error(
                "Updating action for a brok for an unknown host: '%s'",
                host_name)
            return False
        service_name = ''
        if 'service' in brok.data:
            service_name = '__'.join([host_name, brok.data['service']])
            if service_name not in self.mapping['service']:
                logger.error(
                    "Updating action for a brok for an unknown service: '%s'",
                    service_name)
                return False

        data_to_update = {}
        endpoint = 'actionacknowledge'
        if brok.type == 'acknowledge_raise':
            data_to_update['ls_acknowledged'] = True
        elif brok.type == 'acknowledge_expire':
            data_to_update['ls_acknowledged'] = False
        elif brok.type == 'downtime_raise':
            data_to_update['ls_downtimed'] = True
            endpoint = 'actiondowntime'
        elif brok.type == 'downtime_expire':
            data_to_update['ls_downtimed'] = False
            endpoint = 'actiondowntime'

        where = {
            'processed': True,
            'notified': False,
            'host': self.mapping['host'][host_name],
            'comment': brok.data['comment'],
            'service': None
        }

        if 'service' in brok.data:
            # it's a service
            cr = self.send_to_backend('livestate_service', service_name,
                                      data_to_update)
            where['service'] = self.mapping['service'][service_name]
        else:
            # it's a host
            self.send_to_backend('livestate_host', host_name, data_to_update)

        params = {'where': json.dumps(where)}
        self.statsmgr.counter('backend-getall.%s' % endpoint, 1)
        actions = self.backend.get_all(endpoint, params)
        if actions['_items']:
            # case 1: the acknowledge / downtime come from backend, we update the 'notified' field
            # to True
            headers = {
                'Content-Type': 'application/json',
                'If-Match': actions['_items'][0]['_etag']
            }
            self.statsmgr.counter('backend-patch.%s' % endpoint, 1)
            cr = self.backend.patch(
                endpoint + '/' + actions['_items'][0]['_id'],
                {"notified": True}, headers, True)
            return cr['_status'] == 'OK'

        # case 2: the acknowledge / downtime do not come from the backend, it's an external
        # command so we create a new entry
        where['notified'] = True
        # try find the user
        self.statsmgr.counter('backend-getall.user', 1)
        users = self.backend.get_all(
            'user', {'where': '{"name":"' + brok.data['author'] + '"}'})
        if users['_items']:
            where['user'] = users['_items'][0]['_id']
        else:
            logger.error("User '%s' is unknown, ack/downtime is set by admin",
                         brok.data['author'])
            users = self.backend.get_all('user', {'where': '{"name":"admin"}'})
            where['user'] = users['_items'][0]['_id']

        if brok.type in ['acknowledge_raise', 'downtime_raise']:
            where['action'] = 'add'
        else:
            where['action'] = 'delete'
        where['_realm'] = self.ref_live['host'][where['host']]['_realm']

        if endpoint == 'actionacknowledge':
            if brok.data['sticky'] == 2:
                where['sticky'] = False
            else:
                where['sticky'] = True
            where['notify'] = bool(brok.data['notify'])
        elif endpoint == 'actiondowntime':
            where['start_time'] = int(brok.data['start_time'])
            where['end_time'] = int(brok.data['end_time'])
            where['fixed'] = bool(brok.data['fixed'])
            where['duration'] = int(brok.data['duration'])
        self.statsmgr.counter('backend-post.%s' % endpoint, 1)
        cr = self.backend.post(endpoint, where)
        return cr['_status'] == 'OK'

    def send_to_backend(self, type_data, name, data):
        """
        Send data to alignak backend

        :param type_data: one of ['livestate_host', 'livestate_service', 'log_host', 'log_service']
        :type type_data: str
        :param name: name of host or service
        :type name: str
        :param data: dictionary with data to add / update
        :type data: dict
        :return: True if send is ok, False otherwise
        :rtype: bool
        """
        if not self.backend_connected and int(
                time.time() > self.backend_connection_retry_planned):
            self.backend_connected = self.backend_connection()

        if not self.backend_connected:
            logger.error("Alignak backend connection is not available. "
                         "Skipping objects update.")
            return None
        logger.debug("Send to backend: %s, %s", type_data, data)

        headers = {
            'Content-Type': 'application/json',
        }
        ret = True
        if type_data == 'livestate_host':
            headers['If-Match'] = self.ref_live['host'][self.mapping['host']
                                                        [name]]['_etag']
            try:
                start = time.time()
                # self.statsmgr.counter('backend-patch.host', 1)
                response = self.backend.patch(
                    'host/%s' %
                    self.ref_live['host'][self.mapping['host'][name]]['_id'],
                    data, headers, True)
                self.statsmgr.timer('backend-patch-time.host',
                                    time.time() - start)
                if response[
                        '_status'] == 'ERR':  # pragma: no cover - should not happen
                    logger.error('%s', response['_issues'])
                    ret = False
                else:
                    self.ref_live['host'][self.mapping['host']
                                          [name]]['_etag'] = response['_etag']
            except BackendException as exp:  # pragma: no cover - should not happen
                logger.error('Patch livestate for host %s error',
                             self.mapping['host'][name])
                logger.error('Data: %s', data)
                logger.exception("Exception: %s", exp)
                if exp.code == 404:
                    logger.error('Seems the host %s deleted in the Backend',
                                 self.mapping['host'][name])
                elif exp.code == 412:
                    logger.error(
                        'Seems the host %s was modified in the Backend',
                        self.mapping['host'][name])
                    ret = False
                else:
                    self.backend_connected = False
                    self.backend_connection_retry_planned = \
                        int(time.time()) + self.backend_connection_retry_delay
        elif type_data == 'livestate_service':
            service_id = self.mapping['service'][name]
            headers['If-Match'] = self.ref_live['service'][service_id]['_etag']
            try:
                start = time.time()
                self.statsmgr.counter('backend-patch.service', 1)
                logger.debug("Send to backend: %s, %s (_etag: %s) - %s",
                             type_data, name,
                             self.ref_live['service'][service_id]['_etag'],
                             data)
                response = self.backend.patch(
                    'service/%s' % self.ref_live['service'][service_id]['_id'],
                    data, headers, True)
                self.statsmgr.timer('backend-patch-time.service',
                                    time.time() - start)
                if response[
                        '_status'] == 'ERR':  # pragma: no cover - should not happen
                    logger.error('%s', response['_issues'])
                    ret = False
                else:
                    self.ref_live['service'][service_id]['_etag'] = response[
                        '_etag']
                    logger.debug(
                        "Updated _etag: %s, %s (_etag: %s)", type_data, name,
                        self.ref_live['service'][self.mapping['service']
                                                 [name]]['_etag'])
            except BackendException as exp:  # pragma: no cover - should not happen
                logger.error('Patch livestate for %s/%s %s error', type_data,
                             name, self.mapping['service'][name])
                logger.error('Data: %s', data)
                logger.exception("Exception: %s", exp)
                if exp.code == 404:
                    logger.error('Seems the service %s deleted in the Backend',
                                 self.mapping['service'][name])
                elif exp.code == 412:
                    logger.error(
                        'Seems the service %s was modified in the Backend',
                        self.mapping['service'][name])
                    ret = False
                else:
                    self.backend_connected = False
                    self.backend_connection_retry_planned = \
                        int(time.time()) + self.backend_connection_retry_delay
        elif type_data == 'lcrs':
            response = {'_status': 'OK'}
            try:
                logger.debug("Posting %d LCRs to the backend",
                             len(self.logcheckresults))
                while self.logcheckresults:
                    start = time.time()
                    lcrs = self.logcheckresults[:100]
                    self.statsmgr.counter('backend-post.lcr', len(lcrs))
                    response = self.backend.post(endpoint='logcheckresult',
                                                 data=lcrs)
                    logger.debug("Posted %d LCRs", len(lcrs))
                    del self.logcheckresults[:100]
                self.logcheckresults = []
            except BackendException as exp:  # pragma: no cover - should not happen
                logger.error('Error when posting LCR to the backend, data: %s',
                             self.logcheckresults)
                logger.error("Exception: %s", exp)
                self.backend_connected = False
                self.backend_connection_retry_planned = \
                    int(time.time()) + self.backend_connection_retry_delay
            else:
                self.statsmgr.timer('backend-post-time.lcr',
                                    time.time() - start)

                if response[
                        '_status'] == 'ERR':  # pragma: no cover - should not happen
                    logger.error(
                        'Error when posting LCR to the backend, data: %s',
                        self.logcheckresults)
                    logger.error('Issues: %s', response['_issues'])
                    ret = False

        return ret

    def manage_brok(self, brok):
        """
        We get the data to manage

        :param brok: Brok object
        :type brok: object
        :return: False if broks were not managed by the module
        """
        if not self.logged_in:
            if not self.backend_connection():
                logger.debug("Not logged-in, ignoring broks...")
                return False

        brok.prepare()

        logger.debug("manage_brok receives a Brok:")
        logger.debug("\t-Brok: %s - %s", brok.type, brok.data)

        try:
            endpoint = ''
            name = ''
            # Temporary: get concerned item for tracking received broks
            if 'contact_name' in brok.data:
                contact_name = brok.data['contact_name']
                if brok.data['contact_name'] not in self.mapping['user']:
                    logger.debug(
                        "Got a brok %s for an unknown user: '******' (%s)",
                        brok.type, contact_name, brok.data)
                    return False
                endpoint = 'user'
                name = contact_name
            else:
                if 'host_name' in brok.data:
                    host_name = brok.data['host_name']
                    if brok.data['host_name'] not in self.mapping['host']:
                        logger.debug(
                            "Got a brok %s for an unknown host: '%s' (%s)",
                            brok.type, host_name, brok.data)
                        return False
                    endpoint = 'host'
                    name = host_name
                    if 'service_description' in brok.data:
                        service_name = '__'.join(
                            [host_name, brok.data['service_description']])
                        endpoint = 'service'
                        name = service_name
                        if service_name not in self.mapping['service']:
                            logger.debug(
                                "Got a brok %s for an unknown service: '%s' (%s)",
                                brok.type, service_name, brok.data)
                            return False
            if name:
                logger.debug("Received a brok: %s, for %s '%s'", brok.type,
                             endpoint, name)
            else:
                logger.debug("Received a brok: %s", brok.type)
            logger.debug("Brok data: %s", brok.data)

            start = time.time()
            self.statsmgr.counter('managed-broks-type-count.%s' % brok.type, 1)

            ret = False
            if brok.type in ['new_conf']:
                ret = self.get_refs()

            if self.manage_update_program_status and \
                    brok.type in ['program_status', 'update_program_status']:
                self.update_program_status(brok)
                ret = None

            if brok.type == 'host_next_schedule':
                ret = self.update_next_check(brok.data, 'host')
            if brok.type == 'service_next_schedule':
                ret = self.update_next_check(brok.data, 'service')

            if brok.type in [
                    'update_host_status', 'update_service_status',
                    'update_contact_status'
            ]:
                ret = self.update_status(brok)

            if brok.type in ['host_check_result', 'service_check_result']:
                self.check_result(brok.data)
                ret = None

            if brok.type in [
                    'acknowledge_raise', 'acknowledge_expire',
                    'downtime_raise', 'downtime_expire'
            ]:
                ret = self.update_actions(brok)

            self.statsmgr.timer('managed-broks-type-time-%s' % brok.type,
                                time.time() - start)

            return ret
        except Exception as exp:  # pragma: no cover - should not happen
            logger.exception("Manage brok exception: %s", exp)

        return False

    def main(self):
        """
        Main loop of the process

        This module is an "external" module
        :return:
        """
        # Set the OS process title
        self.set_proctitle(self.alias)
        self.set_exit_handler()

        logger.info("starting...")

        while not self.interrupted:
            try:
                queue_size = self.to_q.qsize()
                if queue_size:
                    logger.debug("queue length: %s", queue_size)
                    self.statsmgr.gauge('queue-size', queue_size)

                # Reset backend lists
                self.logcheckresults = []

                message = self.to_q.get_nowait()
                start = time.time()
                for brok in message:
                    # Prepare and manage each brok in the queue message
                    brok.prepare()
                    self.manage_brok(brok)
                self.statsmgr.gauge('managed-broks-count', len(message))

                logger.debug("time to manage %s broks (%d secs)", len(message),
                             time.time() - start)
                self.statsmgr.timer('managed-broks-time', time.time() - start)

                if self.logcheckresults:
                    self.send_to_backend('lcrs', None, None)

            except queue.Empty:
                # logger.debug("No message in the module queue")
                time.sleep(0.1)

        logger.info("stopping...")
        logger.info("stopped")
class AlignakBackendBroker(BaseModule):
    """ This class is used to send logs and livestate to alignak-backend
    """

    def __init__(self, mod_conf):
        """
        Module initialization

        mod_conf is a dictionary that contains:
        - all the variables declared in the module configuration file
        - a 'properties' value that is the module properties as defined globally in this file

        :param mod_conf: module configuration file as a dictionary
        """
        BaseModule.__init__(self, mod_conf)

        # pylint: disable=global-statement
        global logger
        logger = logging.getLogger('alignak.module.%s' % self.alias)

        logger.debug("inner properties: %s", self.__dict__)
        logger.debug("received configuration: %s", mod_conf.__dict__)

        self.url = getattr(mod_conf, 'api_url', 'http://localhost:5000')
        self.backend = Backend(self.url)
        self.backend.token = getattr(mod_conf, 'token', '')
        self.backend_connected = False
        if self.backend.token == '':
            self.getToken(getattr(mod_conf, 'username', ''), getattr(mod_conf, 'password', ''),
                          getattr(mod_conf, 'allowgeneratetoken', False))

        self.logged_in = self.backendConnection()

        self.ref_live = {
            'host': {},
            'service': {}
        }
        self.mapping = {
            'host': {},
            'service': {}
        }
        self.hosts = {}
        self.services = {}
        self.loaded_hosts = False
        self.loaded_services = False

    # Common functions
    def do_loop_turn(self):
        """This function is called/used when you need a module with
        a loop function (and use the parameter 'external': True)
        """
        logger.info("In loop")
        time.sleep(1)

    def getToken(self, username, password, generatetoken):
        """
        Authenticate and get the token

        :param username: login name
        :type username: str
        :param password: password
        :type password: str
        :param generatetoken: if True allow generate token, otherwise not generate
        :type generatetoken: bool
        :return: None
        """
        generate = 'enabled'
        if not generatetoken:
            generate = 'disabled'

        try:
            self.backend.login(username, password, generate)
            self.backend_connected = True
        except BackendException as exp:
            logger.warning("Alignak backend is not available for login. "
                           "No backend connection.")
            logger.exception("Exception: %s", exp)
            self.backend_connected = False

    def backendConnection(self):
        """
        Backend connection to check live state update is allowed

        :return: True/False
        """
        params = {'where': '{"token":"%s"}' % self.backend.token}
        users = self.backend.get('user', params)
        for item in users['_items']:
            return item['can_update_livestate']

        logger.error("Configured user account is not allowed for this module")
        return False

    def get_refs(self, type_data):
        """
        Get the _id in the backend for hosts and services

        :param type_data: livestate type to get: livestate_host or livestate_service
        :type type_data: str
        :return: None
        """
        if type_data == 'livestate_host':
            params = {
                'projection': '{"name":1,"ls_state":1,"ls_state_type":1,"_realm":1}',
                'where': '{"_is_template":false}'
            }
            content = self.backend.get_all('host', params)
            for item in content['_items']:
                self.mapping['host'][item['name']] = item['_id']

                self.ref_live['host'][item['_id']] = {
                    '_id': item['_id'],
                    '_etag': item['_etag'],
                    '_realm': item['_realm'],
                    'initial_state': item['ls_state'],
                    'initial_state_type': item['ls_state_type']
                }
            self.loaded_hosts = True
        elif type_data == 'livestate_service':
            params = {
                'projection': '{"name":1}',
                'where': '{"_is_template":false}'
            }
            contenth = self.backend.get_all('host', params)
            hosts = {}
            for item in contenth['_items']:
                hosts[item['_id']] = item['name']

            params = {
                'projection': '{"host":1,"name":1,"ls_state":1,"ls_state_type":1,"_realm":1}',
                'where': '{"_is_template":false}'
            }
            content = self.backend.get_all('service', params)
            for item in content['_items']:
                self.mapping['service'][''.join([hosts[item['host']],
                                                 item['name']])] = item['_id']

                self.ref_live['service'][item['_id']] = {
                    '_id': item['_id'],
                    '_etag': item['_etag'],
                    '_realm': item['_realm'],
                    'initial_state': item['ls_state'],
                    'initial_state_type': item['ls_state_type']
                }
            self.loaded_services = True

    def update(self, data, obj_type):
        """
        Update livestate_host and livestate_service

        :param data: dictionary of data from scheduler
        :type data: dict
        :param obj_type: type of data (host | service)
        :type obj_type: str
        :return: Counters of updated or add data to alignak backend
        :rtype: dict
        """
        start_time = time.time()
        counters = {
            'livestate_host': 0,
            'livestate_service': 0,
            'log_host': 0,
            'log_service': 0
        }

        logger.debug("Got data to update: %s - %s", obj_type, data)

        if obj_type == 'host':
            if data['host_name'] in self.mapping['host']:
                # Received data for an host:
                # {
                # u'last_time_unreachable': 0, u'last_problem_id': 0, u'check_type': 1,
                # u'retry_interval': 0,u'last_event_id': 0, u'problem_has_been_acknowledged': False,
                # u'command_name': u'nsca_host_dead', u'last_state': u'UP', u'latency': 0,
                # u'last_state_type': u'HARD', u'last_hard_state_change': 0.0,
                # u'last_time_up': 1473597379, u'percent_state_change': 0.0, u'state': u'UP',
                # u'last_chk': 1473597379,
                # u'last_state_id': 0, u'end_time': 0, u'timeout': 0, u'current_event_id': 0,
                # u'execution_time': 0.0, u'start_time': 0, u'return_code': 0,
                # u'state_type': u'HARD', u'state_id': 0, u'in_checking': False,
                # u'early_timeout': 0,
                # u'in_scheduled_downtime': False, u'attempt': 1, u'state_type_id': 1,
                # u'acknowledgement_type': 1, u'last_state_change': 0.0, u'last_time_down': 0,
                # 'instance_id': u'd2d402f5de244d95b10d1b47d9891710', u'long_output': u'',
                # u'current_problem_id': 0, u'host_name': u'fvc320', u'check_interval': 0,
                # u'output': u'No message', u'has_been_checked': 1, u'perf_data': u''
                # }
                data_to_update = {
                    'ls_state': data['state'],
                    'ls_state_id': data['state_id'],
                    'ls_state_type': data['state_type'],
                    'ls_last_check': data['last_chk'],
                    'ls_last_state': data['last_state'],
                    'ls_last_state_type': data['last_state_type'],
                    'ls_output': data['output'],
                    'ls_long_output': data['long_output'],
                    'ls_perf_data': data['perf_data'],
                    'ls_acknowledged': data['problem_has_been_acknowledged'],
                    'ls_downtimed': data['in_scheduled_downtime'],
                    'ls_latency': data['latency']
                }

                h_id = self.mapping['host'][data['host_name']]
                if 'initial_state' in self.ref_live['host'][h_id]:
                    data_to_update['ls_last_state'] = self.ref_live['host'][h_id]['initial_state']
                    data_to_update['ls_last_state_type'] = \
                        self.ref_live['host'][h_id]['initial_state_type']
                    del self.ref_live['host'][h_id]['initial_state']
                    del self.ref_live['host'][h_id]['initial_state_type']

                data_to_update['_realm'] = self.ref_live['host'][h_id]['_realm']
                logger.debug("host live state data: %s", data_to_update)

                # Update live state
                ret = self.send_to_backend('livestate_host', data['host_name'], data_to_update)
                if ret:
                    counters['livestate_host'] += 1

                # Add an host log
                data_to_update['ls_state_changed'] = (
                    data_to_update['ls_state'] != data_to_update['ls_last_state']
                )
                data_to_update['host'] = self.mapping['host'][data['host_name']]
                data_to_update['service'] = None

                # Rename ls_ keys...
                del data_to_update['ls_downtimed']
                for key in data_to_update:
                    if key.startswith('ls_'):
                        data_to_update[key[3:]] = data_to_update[key]
                        del data_to_update[key]

                ret = self.send_to_backend('log_host', data['host_name'], data_to_update)
                if ret:
                    counters['log_host'] += 1
        elif obj_type == 'service':
            service_name = ''.join([data['host_name'], data['service_description']])
            if service_name in self.mapping['service']:
                # Received data for a service:
                # {
                # u'last_problem_id': 0, u'check_type': 0, u'retry_interval': 2,
                # u'last_event_id': 0, u'problem_has_been_acknowledged': False,
                # u'last_time_critical': 1473597376,
                # u'last_time_warning': 0, u'command_name': u'check_nrpe', u'last_state': u'OK',
                # u'latency': 2.4609699249, u'current_event_id': 1, u'last_state_type': u'HARD',
                # u'last_hard_state_change': 0.0, u'percent_state_change': 4.1,
                # u'state': u'CRITICAL',
                # u'last_chk': 1473597375, u'last_state_id': 0, u'host_name': u'denice',
                # u'check_interval': 5, u'last_time_unknown': 0, u'execution_time': 0.1133639812,
                # u'start_time': 0, u'return_code': 2, u'state_type': u'SOFT', u'state_id': 2,
                # u'service_description': u'Disk hda1', u'in_checking': False, u'early_timeout': 0,
                # u'in_scheduled_downtime': False, u'attempt': 1, u'state_type_id': 0,
                # u'acknowledgement_type': 1, u'last_state_change': 1473597376.147903,
                # 'instance_id': u'3ac88dd0c1c04b37a5d181622e93b5bc', u'long_output': u'',
                # u'current_problem_id': 1, u'last_time_ok': 0, u'timeout': 0,
                # u'output': u"NRPE: Command 'check_hda1' not defined", u'has_been_checked': 1,
                # u'perf_data': u'', u'end_time': 0
                # }
                data_to_update = {
                    'ls_state': data['state'],
                    'ls_state_id': data['state_id'],
                    'ls_state_type': data['state_type'],
                    'ls_last_check': data['last_chk'],
                    'ls_last_state': data['last_state'],
                    'ls_last_state_type': data['last_state_type'],
                    'ls_output': data['output'],
                    'ls_long_output': data['long_output'],
                    'ls_perf_data': data['perf_data'],
                    'ls_acknowledged': data['problem_has_been_acknowledged'],
                    'ls_downtimed': data['in_scheduled_downtime'],
                    'ls_execution_time': data['execution_time'],
                    'ls_latency': data['latency']
                }
                s_id = self.mapping['service'][service_name]
                if 'initial_state' in self.ref_live['service'][s_id]:
                    data_to_update['ls_last_state'] = \
                        self.ref_live['service'][s_id]['initial_state']
                    data_to_update['ls_last_state_type'] = \
                        self.ref_live['service'][s_id]['initial_state_type']
                    del self.ref_live['service'][s_id]['initial_state']
                    del self.ref_live['service'][s_id]['initial_state_type']

                data_to_update['_realm'] = self.ref_live['service'][s_id]['_realm']
                logger.debug("service live state data: %s", data_to_update)

                # Update live state
                ret = self.send_to_backend('livestate_service', service_name, data_to_update)
                if ret:
                    counters['livestate_service'] += 1

                # Add a service log
                data_to_update['ls_state_changed'] = (
                    data_to_update['ls_state'] != data_to_update['ls_last_state']
                )
                data_to_update['host'] = self.mapping['host'][data['host_name']]
                data_to_update['service'] = self.mapping['service'][service_name]

                # Rename ls_ keys...
                del data_to_update['ls_downtimed']
                for key in data_to_update:
                    if key.startswith('ls_'):
                        data_to_update[key[3:]] = data_to_update[key]
                        del data_to_update[key]

                self.send_to_backend('log_service', service_name, data_to_update)
                if ret:
                    counters['log_service'] += 1

        if (counters['livestate_host'] + counters['livestate_service']) > 0:
            logger.debug("--- %s seconds ---", (time.time() - start_time))
        return counters

    def send_to_backend(self, type_data, name, data):
        """
        Send data to alignak backend

        :param type_data: one of ['livestate_host', 'livestate_service', 'log_host', 'log_service']
        :type type_data: str
        :param name: name of host or service
        :type name: str
        :param data: dictionary with data to add / update
        :type data: dict
        :return: True if send is ok, False otherwise
        :rtype: bool
        """
        if not self.backend_connected:
            logger.error("Alignak backend connection is not available. "
                         "Skipping objects update.")
            return

        headers = {
            'Content-Type': 'application/json',
        }
        ret = True
        if type_data == 'livestate_host':
            headers['If-Match'] = self.ref_live['host'][self.mapping['host'][name]]['_etag']
            try:
                response = self.backend.patch(
                    'host/%s' % self.ref_live['host'][self.mapping['host'][name]]['_id'],
                    data, headers, True)
                if response['_status'] == 'ERR':
                    logger.error('%s', response['_issues'])
                    ret = False
                else:
                    self.ref_live['host'][self.mapping['host'][name]]['_etag'] = response['_etag']
            except BackendException as exp:
                logger.error('Patch livestate for host %s error', self.mapping['host'][name])
                logger.error('Data: %s', data)
                logger.exception("Exception: %s", exp)
        elif type_data == 'livestate_service':
            headers['If-Match'] = self.ref_live['service'][self.mapping['service'][name]]['_etag']
            try:
                response = self.backend.patch(
                    'service/%s' % self.ref_live['service'][self.mapping['service'][name]]['_id'],
                    data, headers, True)
                if response['_status'] == 'ERR':
                    logger.error('%s', response['_issues'])
                    ret = False
                else:
                    self.ref_live['service'][self.mapping['service'][name]]['_etag'] = response[
                        '_etag']
            except BackendException as exp:
                logger.error('Patch livestate for service %s error', self.mapping['service'][name])
                logger.error('Data: %s', data)
                logger.exception("Exception: %s", exp)
        elif type_data == 'log_host':
            try:
                response = self.backend.post('logcheckresult', data)
            except BackendException as exp:
                logger.error('Post logcheckresult for host %s error', self.mapping['host'][name])
                logger.error('Data: %s', data)
                logger.exception("Exception: %s", exp)
                ret = False
        elif type_data == 'log_service':
            try:
                response = self.backend.post('logcheckresult', data)
            except BackendException as exp:
                logger.error('Post logcheckresult for service %s error',
                             self.mapping['service'][name])
                logger.error('Data: %s', data)
                logger.exception("Exception: %s", exp)
                ret = False
        return ret

    def manage_brok(self, queue):
        """
        We get the data to manage

        :param queue: Brok object
        :type queue: object
        :return: None
        """
        if not self.logged_in:
            logger.debug("Not logged-in, ignoring broks...")
            return

        if not self.loaded_hosts:
            self.get_refs('livestate_host')
        if not self.loaded_services:
            self.get_refs('livestate_service')

        if queue.type == 'host_check_result':
            self.update(queue.data, 'host')
        elif queue.type == 'service_check_result':
            self.update(queue.data, 'service')

    def main(self):
        """
        Main loop of the process

        This module is an "external" module
        :return:
        """
        # Set the OS process title
        self.set_proctitle(self.alias)
        self.set_exit_handler()

        logger.info("starting...")

        while not self.interrupted:
            logger.debug("queue length: %s", self.to_q.qsize())
            start = time.time()
            l = self.to_q.get()
            for b in l:
                b.prepare()
                self.manage_brok(b)

            logger.debug("time to manage %s broks (%d secs)", len(l),
                         time.time() - start)

        logger.info("stopping...")
        logger.info("stopped")
class BackendClient(object):
    """
        Class who collect informations with Backend-Client and returns data for Alignak-App.
    """

    connection_status = {True: 'Success', False: 'Failure'}

    def __init__(self):
        self.backend = None
        self.connected = False
        self.user = {}
        self.ws_client = WSClient()

    def login(self, username=None, password=None, proxies=None, check=False):
        """
        Connect to alignak backend

        :param username: name or token of user
        :type username: str
        :param password: password of user. If token given, this parameter is useless
        :type password: str
        :param proxies: dictionnary for proxy
        :type proxies: dict
        :param check: define if login is a check or a first login
        :type check: bool
        :return: True if connected or False if not
        :rtype: bool
        """

        # Credentials
        if not username and not password:
            if 'token' in self.user:
                username = self.user['token']

        # Create Backend object
        backend_url = settings.get_config('Alignak', 'backend')
        processes = int(settings.get_config('Alignak', 'processes'))

        self.backend = Backend(backend_url, processes=processes)

        logger.debug('Backend URL : %s', backend_url)
        if not check:
            logger.info('Try to connect to the Alignak backend...')

        if username and password:
            # Username & password : not recommended, without login QDialog
            try:
                self.connected = self.backend.login(username,
                                                    password,
                                                    proxies=proxies)
                if self.connected:
                    self.user['username'] = username
                    self.user['token'] = self.backend.token
                logger.info('Connection by password: %s',
                            self.connection_status[self.connected])
            except BackendException:  # pragma: no cover
                logger.error('Connection to Backend has failed !')
        elif username and not password:
            # Username as token : recommended
            if 'token' in self.user:
                self.backend.set_token(self.user['token'])
            else:
                self.backend.set_token(username)
                self.user['token'] = username

            # Make backend connected to test token
            self.connected = True
            connection_test = self.get('user',
                                       {'projection': json.dumps({'name': 1})})

            self.connected = bool(connection_test)
            if not check:
                logger.info('Connection by token: %s',
                            self.connection_status[self.connected])
        else:
            logger.warning(
                'Connection to Backend has failed.\n'
                'Check [Alignak] section in configuration file or use login window of application.'
            )

        if self.connected and not check:
            if settings.get_config('Alignak', 'webservice'):
                self.ws_client.login(self.user['token'])
            else:
                logger.info('No configured Web Service.')

        return self.connected

    def get(self, endpoint, params=None, projection=None, all_items=False):
        """
        GET on alignak Backend REST API

        :param endpoint: endpoint (API URL)
        :type endpoint: str
        :param params: dict of parameters for the app_backend API
        :type params: dict|None
        :param projection: list of field to get, if None, get all
        :type projection: list|None
        :param all_items: make GET on all items
        :type all_items: bool
        :return: request response
        :rtype: dict
        """

        request = None

        if self.connected:
            if params is None:
                params = {'max_results': 50}
            if projection is not None:
                generate_proj = {}
                for field in projection:
                    generate_proj[field] = 1
                params['projection'] = json.dumps(generate_proj)
            # Request
            try:
                if not all_items:
                    request = self.backend.get(endpoint, params)
                else:
                    request = self.backend.get_all(endpoint, params)
                logger.info('GET on [%s] backend > %s', endpoint,
                            str(request['_status']))
                logger.debug('\tparams: [%s]', str(params))
            except BackendException:
                self.connected = False
        else:
            logger.info('App is not connected to backend !')

        return request

    def post(self,
             endpoint,
             data,
             headers=None):  # pragma: no cover - Post already test by client
        """
        POST on alignak Backend REST API

        :param endpoint: endpoint (API URL)
        :type endpoint: str
        :param data: properties of item to create | add
        :type data: dict
        :param headers: headers (example: Content-Type)
        :type headers: dict|None
        :return: response (creation information)
        :rtype: dict
        """

        request = None

        if self.connected:
            try:
                request = self.backend.post(endpoint, data, headers=headers)
                logger.info('POST on [%s] backend > %s', endpoint,
                            str(request['_status']))
                logger.debug('\tdata: [%s]', str(data))
                logger.debug('\theaders: [%s]', str(headers))
            except BackendException:
                self.connected = False
        else:
            logger.info('App is not connected to backend !')

        return request

    def patch(self, endpoint, data, headers):
        """
        PATCH on alignak Backend REST API

        :param endpoint: endpoint (API URL)
        :type endpoint: str
        :param data: properties of item to update
        :type data: dict
        :param headers: headers (example: Content-Type). 'If-Match' required
        :type headers: dict
        :return: dictionary containing patch response from the backend
        :rtype: dict
        """

        request = None

        if self.connected:
            try:
                request = self.backend.patch(endpoint,
                                             data,
                                             headers=headers,
                                             inception=True)
                logger.info('PATCH on [%s] backend > %s', endpoint,
                            str(request['_status']))
                logger.debug('\tdata: [%s]', str(data))
                logger.debug('\theaders: [%s]', str(headers))
            except BackendException:
                self.connected = False
        else:
            logger.info('App is not connected to backend !')

        return request

    def acknowledge(self, item, sticky, notify, comment):  # pragma: no cover
        """
        Prepare data for acknowledge and POST on backend API or WS if available

        :param item: item to acknowledge: host | service
        :type item: alignak_app.items.host.Host | alignak_app.items.service.Service
        :param sticky: define if ack is sticky or not
        :type sticky: bool
        :param notify: define if ack should notify user or not
        :type notify: bool
        :param comment: comment of ack
        :type comment: str
        :return: request response
        :rtype: dict
        """

        user = data_manager.database['user']

        if self.ws_client.auth:
            if item.item_type == 'service':
                command = 'ACKNOWLEDGE_SVC_PROBLEM'
                host = data_manager.get_item('host', '_id', item.data['host'])
                element = host.name
            else:
                command = 'ACKNOWLEDGE_HOST_PROBLEM'
                element = item.name
            item_name = item.name
            if sticky:
                sticky = '2'
            else:
                sticky = '1'
            notify = str(int(notify))
            persistent = '0'

            parameters = ';'.join(
                [item_name, sticky, notify, persistent, user.name, comment])
            data = {
                'command': command,
                'element': element,
                'parameters': parameters
            }
            request = self.ws_client.post('command', params=data)
        else:
            data = {
                'action': 'add',
                'user': user.item_id,
                'comment': comment,
                'notify': notify,
                'sticky': sticky
            }
            if item.item_type == 'service':
                data['host'] = item.data['host']
                data['service'] = item.item_id
            else:
                data['host'] = item.item_id
                data['service'] = None

            request = self.post('actionacknowledge', data)

        return request

    # pylint: disable=too-many-arguments
    def downtime(self, item, fixed, duration, start_stamp, end_stamp,
                 comment):  # pragma: no cover
        """
        Prepare data for downtime and POST on backend API or WS if available

        :param item: item to downtime: host | service
        :type item: alignak_app.items.host.Host | alignak_app.items.service.Service
        :param fixed: define if donwtime is fixed or not
        :type fixed: bool
        :param duration: duration timestamp of downtime
        :type duration: int
        :param start_stamp: start timestamp of downtime
        :type start_stamp: int
        :param end_stamp: end timestamp of downtime
        :type end_stamp: int
        :param comment: comment of downtime
        :type comment: str
        :return: request response
        :rtype: dict
        """

        if self.ws_client.auth:
            if item.item_type == 'service':
                host = data_manager.get_item('host', '_id', item.data['host'])
                element = host.name
            else:
                element = item.name
            fixed = str(int(fixed))
            item_name = item.name
            trigger_id = '0'
            parameters = ';'.join([
                item_name,
                str(start_stamp),
                str(end_stamp), fixed, trigger_id,
                str(duration), data_manager.database['user'].name, comment
            ])
            data = {
                'command':
                'SCHEDULE_SVC_DOWNTIME'
                if item.item_type == 'service' else 'SCHEDULE_HOST_DOWNTIME',
                'element':
                element,
                'parameters':
                parameters
            }
            request = self.ws_client.post('command', params=data)
        else:
            data = {
                'action': 'add',
                'user': data_manager.database['user'].item_id,
                'fixed': fixed,
                'duration': duration,
                'start_time': start_stamp,
                'end_time': end_stamp,
                'comment': comment,
            }

            if item.item_type == 'service':
                data['host'] = item.data['host']
                data['service'] = item.item_id
            else:
                data['host'] = item.item_id
                data['service'] = None

            request = app_backend.post('actiondowntime', data)

        return request

    def query_realms(self):
        """
        Launch a request on ``realm`` endpoint

        """

        request_model = Realm.get_request_model()

        request = self.get(request_model['endpoint'], request_model['params'],
                           request_model['projection'])

        if request:
            realms_list = []
            for backend_item in request['_items']:
                realm = Realm()

                realm.create(
                    backend_item['_id'],
                    backend_item,
                    backend_item['name'],
                )
                realms_list.append(realm)

            if realms_list:
                data_manager.update_database('realm', realms_list)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_timeperiods(self):
        """
        Launch a request on ``timeperiod`` endpoint

        """

        request_model = Period.get_request_model()

        request = self.get(request_model['endpoint'], request_model['params'],
                           request_model['projection'])

        if request:
            periods_list = []
            for backend_item in request['_items']:
                period = Period()

                period.create(
                    backend_item['_id'],
                    backend_item,
                    backend_item['name'],
                )
                periods_list.append(period)

            if periods_list:
                data_manager.update_database('timeperiod', periods_list)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_user(self):
        """
        Launch request on "user" endpoint. Only for current App user.

        """

        request_model = User.get_request_model(self.backend.token)

        request = self.get(request_model['endpoint'], request_model['params'],
                           request_model['projection'])

        if request:
            if request['_items']:
                user = User()

                user.create(request['_items'][0]['_id'], request['_items'][0],
                            request['_items'][0]['name'])

                data_manager.update_database('user', user)

            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_hosts(self):
        """
        Launch request on "host" endpoint, add hosts in problems if needed

        """

        request_model = Host.get_request_model()

        request = self.get(request_model['endpoint'],
                           request_model['params'],
                           request_model['projection'],
                           all_items=True)

        if request:
            hosts_list = []
            for backend_item in request['_items']:
                host = Host()

                host.create(
                    backend_item['_id'],
                    backend_item,
                    backend_item['name'],
                )
                hosts_list.append(host)

                # If host is a problem, add / update it
                if data_manager.is_problem('host', backend_item):
                    if data_manager.get_item('problems', host.item_id):
                        data_manager.update_item_data('problems', host.item_id,
                                                      host.data)
                    else:
                        data_manager.database['problems'].append(host)

            data_manager.db_is_ready['problems']['host'] = True

            if hosts_list:
                data_manager.update_database('host', hosts_list)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_services(self, host_id=None):
        """
        Launch request for "service" endpoint. If ``host_id`` is given, only services related to
        host are added / updated

        :param host_id: "_id" of host
        :type host_id: str
        """

        request_model = Service.get_request_model(host_id)

        request = self.get(request_model['endpoint'],
                           request_model['params'],
                           request_model['projection'],
                           all_items=True)

        if request:
            services_list = []
            for backend_item in request['_items']:
                service = Service()

                service.create(
                    backend_item['_id'],
                    backend_item,
                    backend_item['name'],
                )

                # Add / update only services of host "if host_id"
                if host_id:
                    if not data_manager.get_item('service', service.item_id):
                        logger.debug('Add item data in database[service]')
                        data_manager.database['service'].append(service)
                    else:
                        data_manager.update_item_data('service',
                                                      service.item_id,
                                                      service.data)

            # If not item ID, update all database
            if services_list and not host_id:
                data_manager.update_database('service', services_list)
            if host_id:
                host = data_manager.get_item('host', '_id', host_id)
                if host:
                    logger.info('Update database[service] for %s', host.name)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_services_problems(self, state):
        """
        Launch requests on "service" endpoint to get items with "ls_state = state"

        Wanted states are: ``WARNING``, ``CRITICAL`` and ``UNKNOWN``

        :param state: state of service
        :type state: str
        """

        # Services
        services_projection = [
            'name', 'host', 'alias', 'ls_state', 'ls_output',
            'ls_acknowledged', 'ls_downtimed', 'passive_checks_enabled',
            'active_checks_enabled'
        ]

        params = {
            'where': json.dumps({
                '_is_template': False,
                'ls_state': state
            })
        }
        request = self.get('service',
                           params,
                           services_projection,
                           all_items=True)

        if request:
            for backend_item in request['_items']:
                if data_manager.is_problem('service', backend_item):
                    service = Service()
                    service.create(backend_item['_id'], backend_item,
                                   backend_item['name'])

                    if data_manager.get_item('problems', service.item_id):
                        data_manager.update_item_data('problems',
                                                      service.item_id,
                                                      service.data)
                    else:
                        data_manager.database['problems'].append(service)
            # Problems state is ready
            data_manager.db_is_ready['problems'][state] = True
            logger.info("Update database[problems] for %s services...", state)

    def query_alignakdaemons(self):
        """
        Launch request on "alignakdaemon" endpoint

        """

        request_model = Daemon.get_request_model()

        request = self.get(request_model['endpoint'],
                           request_model['params'],
                           request_model['projection'],
                           all_items=True)

        if request:
            daemons_list = []
            for backend_item in request['_items']:
                daemon = Daemon()

                daemon.create(
                    backend_item['_id'],
                    backend_item,
                    backend_item['name'],
                )

                daemons_list.append(daemon)

            if daemons_list:
                data_manager.update_database('alignakdaemon', daemons_list)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_livesynthesis(self):
        """
        Launch request on "livesynthesis" endpoint

        """

        request_model = LiveSynthesis.get_request_model()

        request = self.get(request_model['endpoint'],
                           request_model['params'],
                           request_model['projection'],
                           all_items=True)

        if request:
            livesynthesis = []
            for backend_item in request['_items']:
                synthesis = LiveSynthesis()

                synthesis.create(
                    backend_item['_id'],
                    backend_item,
                )

                livesynthesis.append(synthesis)

            if livesynthesis:
                data_manager.update_database('livesynthesis', livesynthesis)
            if 'OK' in request['_status']:
                data_manager.db_is_ready[request_model['endpoint']] = True

    def query_history(self, hostname=None, host_id=None):
        """
        Launch request on "history" endpoint but only for hosts in "data_manager"

        :param hostname: name of host we want history
        :type hostname: str
        :param host_id: id of host for history
        :type host_id: str
        """

        request_model = History.get_request_model()

        if hostname and host_id:
            request_model['params']['where'] = json.dumps({'host': host_id})
            request_model['params']['max_results'] = 25

            request = self.get(request_model['endpoint'],
                               request_model['params'],
                               request_model['projection'],
                               all_items=False)

            if request:
                logger.debug('Add / Update history for %s (%s)', hostname,
                             host_id)
                if data_manager.get_item('history', host_id):
                    data_manager.update_item_data('history', host_id,
                                                  request['_items'])
                else:
                    host_history = History()

                    host_history.create(
                        host_id,
                        request['_items'],
                        hostname,
                    )
                    data_manager.database['history'].append(host_history)
        else:  # pragma: no cover, too long to test
            history_list = []
            for history in data_manager.database['history']:
                request_model['params']['where'] = json.dumps(
                    {'host': history.item_id})
                request_model['params']['max_results'] = 25

                request = self.get(request_model['endpoint'],
                                   request_model['params'],
                                   request_model['projection'],
                                   all_items=False)

                if request:
                    host_history = History()

                    host_history.create(
                        history.item_id,
                        request['_items'],
                        history.name,
                    )
                    history_list.append(host_history)

            if history_list:
                data_manager.update_database('history', history_list)

    def query_notifications(
            self):  # pragma: no cover, notifications can be empty
        """
        Launch request on "history" endpoint.
        Only for 'type': 'monitoring.notification' and for current App user

        """

        request_model = Event.get_request_model()

        request = self.get(request_model['endpoint'],
                           request_model['params'],
                           request_model['projection'],
                           all_items=False)

        if request:
            notifications = []
            for backend_item in request['_items']:
                message_split = backend_item['message'].split(';')
                user = message_split[0].split(':')[1].strip()
                if 'imported_admin' in user:
                    user = '******'
                if user == data_manager.database['user'].name:
                    notification = Event()

                    notification.create(
                        backend_item['_id'],
                        backend_item,
                    )

                    notifications.append(notification)

            if notifications:
                data_manager.update_database('notifications', notifications)

    def get_backend_status_icon(self):
        """
        Return backend status icon name

        :return: status icon name
        :rtype: str
        """

        if self.connected:
            return Daemon.get_states('ok')

        return Daemon.get_states('ko')

    def get_ws_status_icon(self):
        """
        Return Web Service status icon name

        :return: status icon name
        :rtype: str
        """

        if self.ws_client.auth:
            return Daemon.get_states('ok')

        return Daemon.get_states('ko')
示例#21
0
class AppBackend(object):
    """
        Class who collect informations with Backend-Client and returns data for
        Alignak-App.
    """
    def __init__(self):
        self.backend = None
        self.user = {}
        self.connected = False
        self.app = None

    def login(self, username=None, password=None):
        """
        Connect to app_backend with credentials in settings.cfg.

        :return: True if connected or False if not
        :rtype: bool
        """

        connect = False
        # Credentials
        if not username and not password:
            if self.user:
                username = self.user['token']
            else:
                username = get_app_config('Alignak', 'username')
                password = get_app_config('Alignak', 'password')

        # Create Backend object
        backend_url = get_app_config('Alignak', 'backend')
        processes = int(get_app_config('Alignak', 'processes'))

        self.backend = Backend(backend_url, processes=processes)

        logger.debug('Backend URL : %s', backend_url)
        logger.info('Try to connect to app_backend...')

        if username and password:
            # Username & password : not recommended, without "widgets.login.py" form.
            try:
                connect = self.backend.login(username, password)
                if connect:
                    self.user['username'] = username
                    self.user['token'] = self.backend.token
                logger.info('Connection by password: %s', str(connect))
            except BackendException as e:  # pragma: no cover
                logger.error('Connection to Backend has failed: %s', str(e))
        elif username and not password:
            # Username as token : recommended
            self.backend.authenticated = True
            if self.user:
                self.backend.token = self.user['token']
            else:
                self.backend.token = username
                self.user['token'] = username

            # Test to check token
            self.connected = True
            connect = bool(self.get('livesynthesis'))
            logger.info('Connection by token: %s', str(connect))
        else:
            # Else exit
            logger.error(
                'Connection to Backend has failed.\nCheck [Backend] section in configuration file.'
            )
            connect = False

        self.connected = connect

        return connect

    def get(self, endpoint, params=None, projection=None):
        """
        GET on alignak Backend REST API.

        :param endpoint: endpoint (API URL)
        :type endpoint: str
        :param params: dict of parameters for the app_backend API
        :type params: dict|None
        :param projection: list of field to get, if None, get all
        :type projection: list|None
        :return desired request of app_backend
        :rtype: dict
        """

        request = None

        if params is None:
            params = {'max_results': 50}
        if projection is not None:
            generate_proj = {}
            for field in projection:
                generate_proj[field] = 1
            params['projection'] = json.dumps(generate_proj)

        if self.connected:
            # Request
            try:
                request = self.backend.get_all(endpoint, params)
                logger.debug('GET: %s', endpoint)
                logger.debug('..with params: %s', str(params))
                logger.debug('...Response > %s', str(request['_status']))
            except BackendException as e:
                logger.error('GET failed: %s', str(e))
                logger.warning(
                    'Application will check the connection with Backend...')
                self.connected = False
                if not self.app.reconnect_mode:
                    self.app.reconnecting.emit(self, str(e))
                return request

        return request

    def post(self,
             endpoint,
             data,
             headers=None):  # pragma: no cover - Post already test by client
        """
        POST on alignak Backend REST API

        :param endpoint: endpoint (API URL)
        :type endpoint: str
        :param data: properties of item to create | add
        :type data: dict
        :param headers: headers (example: Content-Type)
        :type headers: dict|None
        :return: response (creation information)
        :rtype: dict
        """

        resp = None

        if self.connected:
            try:
                resp = self.backend.post(endpoint, data, headers=headers)
                logger.debug('POST on %s', endpoint)
                logger.debug('..with data: %s', str(data))
                logger.debug('...Response > %s', str(resp))
            except BackendException as e:
                logger.error('POST failed: %s', str(e))
                logger.warning(
                    'Application will check the connection with Backend...')
                self.connected = False
                if not self.app.reconnect_mode:
                    self.app.reconnecting.emit(self, str(e))
                return resp

        return resp

    def get_host(self, key, value, projection=None):
        """
        Return the host corresponding to "key"/"value" pair

        :param key: key corresponding to value
        :type key: str
        :param value: value of key
        :type value: str
        :param projection: list of field to get, if None, get all
        :type projection: list|None
        :return: None if not found or item dict
        :rtype: dict|None
        """

        params = {'where': json.dumps({'_is_template': False, key: value})}

        hosts = self.get('host', params, projection=projection)

        if hosts and len(hosts['_items']) > 0:  # pylint: disable=len-as-condition
            wanted_host = hosts['_items'][0]
        else:
            wanted_host = None

        return wanted_host

    def get_service(self, host_id, service_id, projection=None):
        """
        Returns the desired service of the specified host

        :param host_id: "_id" of host
        :type host_id: str
        :param service_id: "_id" of wanted service
        :type service_id: str
        :param projection: list of field to get, if None, get all
        :type projection: list|None
        :return: wanted service
        :rtype: dict
        """

        params = {
            'where': json.dumps({
                '_is_template': False,
                'host': host_id
            })
        }

        services = self.get('service', params=params, projection=projection)

        wanted_service = None
        if services:
            if len(services['_items']) > 0:  # pylint: disable=len-as-condition
                wanted_service = services['_items'][0]
            for service in services['_items']:
                if service['_id'] == service_id:
                    wanted_service = service

        return wanted_service

    def get_host_with_services(self, host_name):
        """
        Returns the desired host and all its services

        :param host_name: desired host
        :type host_name: str
        :return dict with host data and its associated services
        :rtype: dict
        """

        host_data = None

        host_projection = [
            'name', 'alias', 'ls_state', '_id', 'ls_acknowledged',
            'ls_downtimed', 'ls_last_check', 'ls_output', 'address',
            'business_impact', 'parents', 'ls_last_state_changed'
        ]
        host = self.get_host('name', host_name, projection=host_projection)

        if host:
            params = {
                'where': json.dumps({
                    '_is_template': False,
                    'host': host['_id']
                })
            }
            service_projection = [
                'name', 'alias', 'display_name', 'ls_state', 'ls_acknowledged',
                'ls_downtimed', 'ls_last_check', 'ls_output',
                'business_impact', 'customs', '_overall_state_id',
                'aggregation', 'ls_last_state_changed'
            ]
            services = self.get('service',
                                params=params,
                                projection=service_projection)

            services_host = services['_items']

            host_data = {'host': host, 'services': services_host}

        return host_data

    def get_user(self, projection=None):
        """
        Get current user. The token must already be acquired

        :param projection: list of field to get, if None, get all
        :type projection: list|None
        :return user items
        :rtype dict|None
        """

        params = {'where': json.dumps({'token': self.user['token']})}

        if projection is not None:
            generate_proj = {}
            for field in projection:
                generate_proj[field] = 1
            params['projection'] = json.dumps(generate_proj)

        user = self.get('user', params, projection=projection)

        if user:
            return user['_items'][0]

        return None

    def synthesis_count(self):
        """
        Get on "synthesis" endpoint and return the states of hosts and services

        :return: states of hosts and services.
        :rtype: dict
        """

        states = {
            'hosts': {
                'up': 0,
                'down': 0,
                'unreachable': 0,
                'acknowledge': 0,
                'downtime': 0
            },
            'services': {
                'ok': 0,
                'critical': 0,
                'unknown': 0,
                'warning': 0,
                'unreachable': 0,
                'acknowledge': 0,
                'downtime': 0
            }
        }
        live_synthesis = self.get('livesynthesis')

        if live_synthesis:
            for realm in live_synthesis['_items']:
                states['hosts']['up'] += realm['hosts_up_soft']
                states['hosts']['up'] += realm['hosts_up_hard']

                states['hosts']['unreachable'] += realm[
                    'hosts_unreachable_soft']
                states['hosts']['unreachable'] += realm[
                    'hosts_unreachable_hard']

                states['hosts']['down'] += realm['hosts_down_soft']
                states['hosts']['down'] += realm['hosts_down_hard']

                states['hosts']['acknowledge'] += realm['hosts_acknowledged']
                states['hosts']['downtime'] += realm['hosts_in_downtime']

                states['services']['ok'] += realm['services_ok_soft']
                states['services']['ok'] += realm['services_ok_hard']

                states['services']['warning'] += realm['services_warning_soft']
                states['services']['warning'] += realm['services_warning_hard']

                states['services']['critical'] += realm[
                    'services_critical_soft']
                states['services']['critical'] += realm[
                    'services_critical_hard']

                states['services']['unknown'] += realm['services_unknown_soft']
                states['services']['unknown'] += realm['services_unknown_hard']

                states['services']['unreachable'] += realm[
                    'services_unreachable_soft']
                states['services']['unreachable'] += realm[
                    'services_unreachable_hard']

                states['services']['acknowledge'] += realm[
                    'services_acknowledged']
                states['services']['downtime'] += realm['services_in_downtime']

        logger.info('Store current states...')

        return states
    def test_04_connection_username(self):
        global backend_address

        print ''
        print 'test accepted connection with username/password'

        # Create client API
        backend = Backend(backend_address)

        print 'Login ...'
        result = backend.login('admin', 'admin')
        print 'authenticated:', backend.authenticated
        print 'token:', backend.token
        assert_true(backend.authenticated)

        print 'Logout ...'
        result = backend.logout()
        print 'authenticated:', backend.authenticated
        print 'token:', backend.token
        assert_false(backend.authenticated)

        print 'Login ...'
        print 'authenticated:', backend.authenticated
        result = backend.login('admin', 'admin')
        print 'authenticated:', backend.authenticated
        print 'token:', backend.token
        assert_true(backend.authenticated)

        print 'Logout ...'
        result = backend.logout()
        print 'authenticated:', backend.authenticated
        print 'token:', backend.token
        assert_false(backend.authenticated)

        print 'Logout ...'
        result = backend.logout()
        print 'authenticated:', backend.authenticated
        print 'token:', backend.token
        assert_false(backend.authenticated)

        print 'get object ... must be refused!'
        with assert_raises(BackendException) as cm:
            items = backend.get('host')
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 1001, str(ex))

        print 'get_all object ... must be refused!'
        with assert_raises(BackendException) as cm:
            items = backend.get_all('host')
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 1001, str(ex))

        print 'get all domains ... must be refused!'
        with assert_raises(BackendException) as cm:
            items = backend.get_domains()
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 1001, str(ex))

        print 'post data ... must be refused!'
        with assert_raises(BackendException) as cm:
            data = { 'fake': 'fake' }
            response = backend.post('contact', data=data)
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 1001, str(ex))

        print 'patch data ... must be refused!'
        with assert_raises(BackendException) as cm:
            data = { 'fake': 'fake' }
            headers = { 'If-Match': '' }
            response = backend.patch('contact', data=data, headers=headers)
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 1001, str(ex))

        print 'delete data ... must be refused!'
        with assert_raises(BackendException) as cm:
            data = { 'fake': 'fake' }
            headers = { 'If-Match': '' }
            response = backend.delete('contact', headers=headers)
        ex = cm.exception
        print 'exception:', str(ex.code)
        assert_true(ex.code == 1001, str(ex))
    def test_04_login(self):
        """
        Test with right username / password

        :return: None
        """
        print('')
        print('test accepted connection with username/password')

        # Create client API
        backend = Backend(self.backend_address)

        print('Login ...')
        assert backend.login('admin', 'admin')
        print('authenticated:', backend.authenticated)
        print('token:', backend.token)
        assert_true(backend.authenticated)

        print('Logout ...')
        backend.logout()
        print('authenticated:', backend.authenticated)
        print('token:', backend.token)
        assert_false(backend.authenticated)

        print('Login ...')
        print('authenticated:', backend.authenticated)
        assert backend.login('admin', 'admin')
        print('authenticated:', backend.authenticated)
        print('token:', backend.token)
        assert_true(backend.authenticated)

        print('Logout ...')
        backend.logout()
        print('authenticated:', backend.authenticated)
        print('token:', backend.token)
        assert_false(backend.authenticated)

        print('Logout ...')
        backend.logout()
        print('authenticated:', backend.authenticated)
        print('token:', backend.token)
        assert_false(backend.authenticated)

        print('get object ... must be refused!')
        with assert_raises(BackendException) as cm:
            backend.get('host')
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 401, str(ex))

        print('get_all object ... must be refused!')
        with assert_raises(BackendException) as cm:
            backend.get_all('host')
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 401, str(ex))

        print('get all domains ... must be refused!')
        with assert_raises(BackendException) as cm:
            backend.get_domains()
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 401, str(ex))

        print('post data ... must be refused!')
        with assert_raises(BackendException) as cm:
            data = {'fake': 'fake'}
            backend.post('user', data=data)
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 401, str(ex))

        print('patch data ... must be refused!')
        with assert_raises(BackendException) as cm:
            data = {'fake': 'fake'}
            headers = {'If-Match': ''}
            backend.patch('user', data=data, headers=headers)
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 405, str(ex))

        print('delete data ... must be refused!')
        with assert_raises(BackendException) as cm:
            headers = {'If-Match': ''}
            backend.delete('user', headers=headers)
        ex = cm.exception
        print('exception:', str(ex.code))
        assert_true(ex.code == 401, str(ex))
class AlignakBackendScheduler(BaseModule):
    """
    This class is used to send live states to alignak-backend
    """

    def __init__(self, mod_conf):
        """
        Module initialization

        mod_conf is a dictionary that contains:
        - all the variables declared in the module configuration file
        - a 'properties' value that is the module properties as defined globally in this file

        :param mod_conf: module configuration file as a dictionary
        """
        BaseModule.__init__(self, mod_conf)

        # pylint: disable=global-statement
        global logger
        logger = logging.getLogger("alignak.module.%s" % self.alias)

        logger.debug("inner properties: %s", self.__dict__)
        logger.debug("received configuration: %s", mod_conf.__dict__)

        self.url = getattr(mod_conf, "api_url", "http://localhost:5000")
        self.backend = Backend(self.url)
        self.backend.token = getattr(mod_conf, "token", "")
        self.backend_connected = False
        if self.backend.token == "":
            self.getToken(
                getattr(mod_conf, "username", ""),
                getattr(mod_conf, "password", ""),
                getattr(mod_conf, "allowgeneratetoken", False),
            )

    # Common functions
    def do_loop_turn(self):
        """This function is called/used when you need a module with
        a loop function (and use the parameter 'external': True)
        """
        logger.info("[Backend Scheduler] In loop")
        time.sleep(1)

    def getToken(self, username, password, generatetoken):
        """
        Authenticate and get the token

        :param username: login name
        :type username: str
        :param password: password
        :type password: str
        :param generatetoken: if True allow generate token, otherwise not generate
        :type generatetoken: bool
        :return: None
        """
        generate = "enabled"
        if not generatetoken:
            generate = "disabled"

        try:
            self.backend.login(username, password, generate)
            self.backend_connected = True
        except BackendException as exp:
            logger.warning("Alignak backend is not available for login. " "No backend connection.")
            logger.exception("Exception: %s", exp)
            self.backend_connected = False

    def hook_load_retention(self, scheduler):
        """
        Load retention data from alignak-backend

        :param scheduler: scheduler instance of alignak
        :type scheduler: object
        :return: None
        """

        all_data = {"hosts": {}, "services": {}}
        if not self.backend_connected:
            logger.error(
                "[Backend Scheduler] Alignak backend connection is not available. " "Skipping objects retention load."
            )
        else:
            # Get data from the backend
            response = self.backend.get_all("retentionhost")
            for host in response["_items"]:
                # clean unusable keys
                hostname = host["host"]
                for key in ["_created", "_etag", "_id", "_links", "_updated", "host"]:
                    del host[key]
                all_data["hosts"][hostname] = host
            response = self.backend.get_all("retentionservice")
            for service in response["_items"]:
                # clean unusable keys
                servicename = (service["service"][0], service["service"][1])
                for key in ["_created", "_etag", "_id", "_links", "_updated", "service"]:
                    del service[key]
                all_data["services"][servicename] = service

        scheduler.restore_retention_data(all_data)

    def hook_save_retention(self, scheduler):
        """
        Save retention data from alignak-backend

        :param scheduler: scheduler instance of alignak
        :type scheduler: object
        :return: None
        """
        data_to_save = scheduler.get_retention_data()

        if not self.backend_connected:
            logger.error("Alignak backend connection is not available. " "Skipping objects retention save.")
            return

        # clean hosts we will re-upload the retention
        response = self.backend.get_all("retentionhost")
        for host in response["_items"]:
            if host["host"] in data_to_save["hosts"]:
                delheaders = {"If-Match": host["_etag"]}
                self.backend.delete("/".join(["retentionhost", host["_id"]]), headers=delheaders)

        # Add all hosts after
        for host in data_to_save["hosts"]:
            data_to_save["hosts"][host]["host"] = host
            try:
                self.backend.post("retentionhost", data=data_to_save["hosts"][host])
            except BackendException as exp:
                logger.error("Post retentionhost for host error")
                logger.error("Response: %s", exp.response)
                logger.exception("Exception: %s", exp)
                return
        logger.info("%d hosts saved in retention", len(data_to_save["hosts"]))

        # clean services we will re-upload the retention
        response = self.backend.get_all("retentionservice")
        for service in response["_items"]:
            if (service["service"][0], service["service"][1]) in data_to_save["services"]:
                delheaders = {"If-Match": service["_etag"]}
                self.backend.delete("/".join(["retentionservice", service["_id"]]), headers=delheaders)

        # Add all services after
        for service in data_to_save["services"]:
            data_to_save["services"][service]["service"] = service
            try:
                self.backend.post("retentionservice", data=data_to_save["services"][service])
            except BackendException as exp:
                logger.error("Post retentionservice for service error")
                logger.exception("Exception: %s", exp)
                return
        logger.info("%d services saved in retention", len(data_to_save["services"]))