Пример #1
0
class Monitor:
    DEFAULTS = {
        'env': 'WebEX-Mirantis@Cisco',
        'inventory': 'inventory',
        'loglevel': 'WARNING'
    }

    def __init__(self):
        self.args = self.get_args()
        MongoAccess.set_config_file(self.args.mongo_config)
        self.inv = InventoryMgr()
        self.inv.set_collections(self.args.inventory)
        self.configuration = Configuration()
        self.input_text = None
        self.converter = SpecialCharConverter()

    def get_args(self):
        parser = argparse.ArgumentParser()
        parser.add_argument("-m",
                            "--mongo_config",
                            nargs="?",
                            type=str,
                            default="",
                            help="name of config file with MongoDB server " +
                            "access details")
        parser.add_argument("-e",
                            "--env",
                            nargs="?",
                            type=str,
                            default=self.DEFAULTS['env'],
                            help="name of environment to scan \n" +
                            "(default: {})".format(self.DEFAULTS['env']))
        parser.add_argument("-y",
                            "--inventory",
                            nargs="?",
                            type=str,
                            default=self.DEFAULTS['inventory'],
                            help="name of inventory collection \n" +
                            "(default: {}".format(self.DEFAULTS['inventory']))
        parser.add_argument('-i',
                            '--inputfile',
                            nargs='?',
                            type=str,
                            default='',
                            help="read input from the specifed file \n" +
                            "(default: from stdin)")
        parser.add_argument("-l",
                            "--loglevel",
                            nargs="?",
                            type=str,
                            default=self.DEFAULTS["loglevel"],
                            help="logging level \n(default: '{}')".format(
                                self.DEFAULTS["loglevel"]))
        args = parser.parse_args()
        return args

    def get_type_list(self, type_name) -> list:
        types_list = []
        docs = self.inv.find_items({'name': type_name}, collection='constants')
        for types_list in docs:
            types_list = [t['value'] for t in types_list['data']]
        if not types_list:
            raise ValueError('Unable to fetch {}'.format(
                type_name.replace('_', ' ')))
        return types_list

    def match_object_types(self, check_name: str) -> list:
        object_types = self.get_type_list('object_types')
        matches = [t for t in object_types if check_name.startswith(t + '_')]
        return matches

    def match_link_types(self, check_name: str) -> list:
        object_types = self.get_type_list('link_types')
        matches = [
            t for t in object_types if check_name.startswith('link_' + t + '_')
        ]
        return matches

    def find_object_type_and_id(self, check_name: str):
        # if we have multiple matching host types, then take the longest
        # of these. For example, if matches are ['host', 'host_pnic'],
        # then take 'host_pnic'.
        # To facilitate this, we sort the matches by reverse order.
        is_link_check = check_name.startswith('link_')
        check_type = 'link' if is_link_check else 'object'
        if is_link_check:
            matching_types = sorted(self.match_link_types(check_name),
                                    reverse=True)
        else:
            matching_types = sorted(self.match_object_types(check_name),
                                    reverse=True)
        if not matching_types:
            raise ValueError(
                'Unable to match check name "{}" with {} type'.format(
                    check_name, check_type))
        obj_type = matching_types[0]
        postfix_len = len('link_') if is_link_check else 0
        obj_id = (obj_type + '_' if is_link_check else '') + \
            check_name[len(obj_type)+1+postfix_len:]
        return check_type, obj_type, obj_id

    def read_input(self):
        if self.args.inputfile:
            try:
                with open(self.args.inputfile, 'r') as input_file:
                    self.input_text = input_file.read()
            except Exception as e:
                raise FileNotFoundError(
                    "failed to open input file {}: {}".format(
                        self.args.inputfile, str(e)))
        else:
            self.input_text = sys.stdin.read()
            if not self.input_text:
                raise ValueError("No input provided on stdin")

    def get_handler_by_type(self, check_type, obj_type):
        module_name = 'handle_link' if check_type == 'link' \
                else 'handle_' + obj_type
        package = 'monitoring.handlers'
        handler = ClassResolver.get_instance_single_arg(
            self.args, module_name=module_name, package_name=package)
        return handler

    def get_handler(self, check_type, obj_type):
        basic_handling_types = ['instance', 'vedge', 'vservice', 'vconnector']
        if obj_type not in basic_handling_types:
            return self.get_handler_by_type(check_type, obj_type)
        from monitoring.handlers.basic_check_handler \
            import BasicCheckHandler
        return BasicCheckHandler(self.args)

    def check_link_interdependency_for(self,
                                       object_id: str,
                                       from_type: str = None,
                                       to_type: str = None):
        if from_type is not None and to_type is not None or \
                from_type is None and to_type is None:
            raise ValueError('check_link_interdependency: '
                             'supply one of from_type/to_type')
        obj_id = self.converter.decode_special_characters(object_id)
        obj = self.inv.get_by_id(environment=self.args.env, item_id=obj_id)
        if not obj:
            self.inv.log.error(
                'check_link_interdependency: '
                'failed to find object with ID: {}'.format(object_id))
            return
        if 'status' not in obj:
            return
        id_attr = 'source_id' if from_type is None else 'target_id'
        link_type = '{}-{}'.format(
            from_type if from_type is not None else obj['type'],
            to_type if to_type is not None else obj['type'])
        condition = {
            'environment': self.args.env,
            'link_type': link_type,
            id_attr: obj_id
        }
        link = self.inv.find_one(search=condition, collection='links')
        if not link:
            self.inv.log.error('check_link_interdependency: '
                               'failed to find {} link with {}: {}'.format(
                                   link_type, id_attr, obj_id))
            return
        other_id_attr = '{}_id' \
            .format('source' if from_type is not None else 'target')
        other_obj = self.inv.get_by_id(environment=self.args.env,
                                       item_id=link[other_id_attr])
        if not other_obj:
            self.inv.log.error(
                'check_link_interdependency: '
                'failed to find {} with ID: {} (link type: {})'.format(
                    other_id_attr, link[other_id_attr], link_type))
            return
        if 'status' not in other_obj:
            return
        status = 'Warning'
        if obj['status'] == 'OK' and other_obj['status'] == 'OK':
            status = 'OK'
        elif obj['status'] == 'OK' and other_obj['status'] == 'OK':
            status = 'OK'
        link['status'] = status
        time_format = MonitoringCheckHandler.TIME_FORMAT
        timestamp1 = obj['status_timestamp']
        t1 = datetime.datetime.strptime(timestamp1, time_format)
        timestamp2 = other_obj['status_timestamp']
        t2 = datetime.datetime.strptime(timestamp2, time_format)
        timestamp = max(t1, t2)
        link['status_timestamp'] = datetime.datetime.strftime(
            timestamp, time_format)
        self.inv.set(link, self.inv.collections['links'])

    def check_link_interdependency(self, object_id: str, object_type: str):
        conf = self.configuration.get_env_config()
        if 'OVS' in conf['mechanism_drivers']:
            if object_type == 'vedge':
                self.check_link_interdependency_for(object_id,
                                                    to_type='host_pnic')
            if object_type == 'host_pnic':
                self.check_link_interdependency_for(object_id,
                                                    from_type='vedge')

    def process_input(self):
        check_result_full = json.loads(self.input_text)
        check_client = check_result_full['client']
        check_result = check_result_full['check']
        check_result['id'] = check_result_full['id']
        name = check_result['name']
        check_type, object_type, object_id = \
            monitor.find_object_type_and_id(name)
        if 'environment' in check_client:
            self.args.env = check_client['environment']
        else:
            raise ValueError('Check client should contain environment name')
        self.configuration.use_env(self.args.env)

        check_handler = self.get_handler(check_type, object_type)
        if check_handler:
            check_handler.handle(object_id, check_result)
        self.check_link_interdependency(object_id, object_type)

    def process_check_result(self):
        self.read_input()
        self.process_input()
Пример #2
0
class CliFetchBondHostPnics(CliAccess):
    BOND_DIR = '/proc/net/bonding/'
    SLAVE_INTERFACE_HEADER = 'Slave Interface: '

    def __init__(self):
        super().__init__()
        self.inv = InventoryMgr()

    def get(self, parent_id: str):
        self.log.info(
            'CliFetchBondHostPnics: checking under {}'.format(parent_id))
        host_id = parent_id[:parent_id.rindex('-')]
        cmd = 'ls -1 {} 2>&1'.format(self.BOND_DIR)
        host = self.inv.get_by_id(self.get_env(), host_id)
        if not host:
            self.log.error('CliFetchBondHostPnics: host not found: ' + host_id)
            return []
        host_types = host['host_type']
        if 'Network' not in host_types and 'Compute' not in host_types:
            return []
        lines = self.run_fetch_lines(cmd, host_id)
        if lines and 'No such file or directory' in lines[0]:
            return []  # no bonds so directory does not exist
        bonds = []
        for line in [l.strip() for l in lines]:
            bond = self.get_bond_details(host_id, line)
            if bond:
                bonds.append(bond)
        return bonds

    def get_bond_details(self, host_id: str, interface_name: str) -> dict:
        lines = self.run_fetch_lines(
            'cat {}{}'.format(self.BOND_DIR, interface_name), host_id)
        status, mac_address = \
            self.get_bond_status_and_mac_address(host_id, interface_name)
        interface_id = '{}-{}'.format(interface_name, mac_address)
        interface = {
            'host': host_id,
            'name': interface_name,
            'id': interface_id,
            'local_name': interface_name,
            'mac_address': mac_address,
            'Link detected': 'yes' if status == 'up' else 'no',
            'EtherChannel': True,
            'EtherChannel Master': '',
            'members': {}
        }
        # keep stack of info objects to support multi-level info
        info_objects = deque([interface])
        for line in [line for line in lines if line != '']:
            if line.startswith(self.SLAVE_INTERFACE_HEADER):
                name = line[line.index(':') + 1:].strip()
                slave = {'name': name, 'EtherChannel Master': interface_id}
                # remove any pending info objects, keep only interface
                info_objects = deque([interface])
                info_objects.append(slave)
                interface['members'][name] = slave
            elif line.rstrip(':').lower().endswith('info'):
                # move to lower level info object
                info_name = line.rstrip(':')
                upper_info_obj = info_objects[-1]
                info_obj = {}
                upper_info_obj[info_name] = info_obj
                info_objects.append(info_obj)
            else:
                self.get_attribute_from_line(info_objects[-1], line)
        for slave in list(interface['members'].values()):
            self.set_slave_host_pnic_bond_attributes(host_id, slave,
                                                     interface_id)
        return interface

    def get_bond_status_and_mac_address(self, host_id: str, name: str):
        output = self.run_fetch_lines('ip link show {}'.format(name), host_id)
        status_line = output[0]
        status = status_line[status_line.index(' state ') + len(' state '):]
        status = status[:status.index(' ')]
        matches = [line.strip() for line in output if 'link/ether' in line]
        if not matches:
            self.log.error('Failed to find line with MAC address '
                           'for bond {} (host: {})'.format(name, host_id))
        tokens = matches[0].split()
        if len(tokens) < 2:
            self.log.error('Failed to find MAC address in line: {}'.format(
                matches[0]))
        mac_address = tokens[1]
        return status.lower(), mac_address

    def get_attribute_from_line(self, obj: dict, line: str):
        if ':' not in line:
            self.log.error('object {}: failed to find ":" in line: {}'.format(
                obj['name'], line))
            return
        attr = line[:line.index(':')]
        value = line[len(attr) + 1:]
        obj[attr.strip()] = value.strip()

    def set_slave_host_pnic_bond_attributes(self, host, slave, interface_id):
        pnic = self.inv.find_one({
            'environment': self.get_env(),
            'host': host,
            'type': 'host_pnic',
            'name': slave['name']
        })
        if not pnic:
            self.log.error('unable to find slave pNIC {} under bond {}'.format(
                slave['name'], interface_id))
            return
        mac_address = pnic['mac_address']
        slave_id = '{}-{}'.format(slave.get('name', ''), mac_address)
        slave['mac_address'] = mac_address
        slave['id'] = slave_id
        pnic['EtherChannel'] = True
        pnic['EtherChannel Master'] = interface_id
        self.inv.set(pnic)
Пример #3
0
class MonitoringCheckHandler(SpecialCharConverter):
    status_labels = {}
    TIME_FORMAT = '%Y-%m-%d %H:%M:%S %Z'

    def __init__(self, args):
        super().__init__()
        self.log = FullLogger()
        self.log.set_loglevel(args.loglevel)
        self.env = args.env
        try:
            self.conf = Configuration(args.mongo_config)
            self.inv = InventoryMgr()
            self.inv.log.set_loglevel(args.loglevel)
            self.inv.set_collections(args.inventory)
            self.status_labels = self.get_status_labels()
        except FileNotFoundError:
            sys.exit(1)

    def get_status_labels(self):
        statuses_name_search = {'name': 'monitoring_check_statuses'}
        labels_data = self.inv.find_one(search=statuses_name_search,
                                        collection='constants')
        if not isinstance(labels_data, dict) or 'data' not in labels_data:
            return ''
        labels = {}
        for status_data in labels_data['data']:
            if not isinstance(status_data, dict):
                continue
            val = int(status_data['value'])
            label = status_data['label']
            labels[val] = label
        return labels

    def get_label_for_status(self, status: int) -> str:
        if status not in self.status_labels.keys():
            return ''
        return self.status_labels.get(status, '')

    def doc_by_id(self, object_id):
        doc = self.inv.get_by_id(self.env, object_id)
        if not doc:
            self.log.warn('No matching object found with ID: ' + object_id)
        return doc

    def doc_by_db_id(self, db_id, coll_name=None):
        coll = self.inv.collections[coll_name] if coll_name else None
        doc = self.inv.find({'_id': ObjectId(db_id)},
                            get_single=True,
                            collection=coll)
        if not doc:
            self.log.warn('No matching object found with DB ID: ' + db_id)
        return doc

    def set_doc_status(self, doc, status, status_text, timestamp):
        doc['status_value'] = status if isinstance(status, int) \
            else status
        doc['status'] = self.get_label_for_status(status) \
            if isinstance(status, int) \
            else status
        if status_text:
            doc['status_text'] = status_text
        doc['status_timestamp'] = strftime(self.TIME_FORMAT, timestamp)
        if 'link_type' in doc:
            self.inv.write_link(doc)
        else:
            self.inv.set(doc)

    @staticmethod
    def check_ts(check_result):
        return gmtime(check_result['executed'])

    def keep_result(self, doc, check_result):
        status = check_result['status']
        ts = self.check_ts(check_result)
        self.set_doc_status(doc, status, check_result['output'], ts)
        self.keep_message(doc, check_result)

    def keep_message(self, doc, check_result, error_level=None):
        is_link = 'link_type' in doc
        msg_id = check_result['id']
        obj_id = 'link_{}_{}'.format(doc['source_id'], doc['target_id']) \
            if is_link \
            else doc['id']
        obj_type = 'link_{}'.format(doc['link_type']) if is_link else \
            doc['type']
        display_context = obj_id if is_link \
            else doc['network_id'] if doc['type'] == 'port' else doc['id']
        level = error_level if error_level\
            else ERROR_LEVEL[check_result['status']]
        dt = datetime.datetime.utcfromtimestamp(check_result['executed'])
        message = Message(msg_id=msg_id,
                          env=self.env,
                          source=SOURCE_SYSTEM,
                          object_id=obj_id,
                          object_type=obj_type,
                          display_context=display_context,
                          level=level,
                          msg=check_result,
                          ts=dt)
        collection = self.inv.collections['messages']
        collection.insert_one(message.get())