class CliFetchHostPnicsVpp(CliAccess):
    def __init__(self):
        super().__init__()
        self.inv = InventoryMgr()
        self.name_re = re.compile(NAME_RE)

    def get(self, id):
        host_id = id[:id.rindex("-")]
        host_id = id[:host_id.rindex("-")]
        vedges = self.inv.find_items({
            "environment": self.get_env(),
            "type": "vedge",
            "host": host_id
        })
        ret = []
        for vedge in vedges:
            pnic_ports = vedge['ports']
            for pnic_name in pnic_ports:
                if not self.name_re.search(pnic_name):
                    continue
                pnic = pnic_ports[pnic_name]
                pnic['host'] = host_id
                pnic['id'] = host_id + "-pnic-" + pnic_name
                pnic['type'] = 'host_pnic'
                pnic['object_name'] = pnic_name
                self.get_pnic_mac_address(pnic)
                pnic[
                    'Link detected'] = 'yes' if pnic['state'] == 'up' else 'no'
                ret.append(pnic)
        return ret

    def get_pnic_mac_address(self, pnic):
        cmd = 'vppctl show hardware-interfaces {}'.format(pnic['object_name'])
        output_lines = self.run_fetch_lines(cmd, ssh_to_host=pnic['host'])
        if output_lines:
            regexps = [{'name': 'mac_address', 're': MAC_FIELD_RE}]
            for line in output_lines:
                self.find_matching_regexps(pnic, line, regexps)
                if 'mac_address' in pnic:
                    break
Exemplo n.º 2
0
class DbFetchVedgesOvs(DbAccess, CliAccess, metaclass=Singleton):
    def __init__(self):
        super().__init__()
        self.inv = InventoryMgr()
        self.port_re = re.compile("^\s*port (\d+): ([^(]+)( \(internal\))?$")
        self.port_line_header_prefix = " " * 8 + "Port "

    def get(self, parent_id):
        host_id = parent_id[:parent_id.rindex('-')]
        results = self.get_objects_list_for_id(
            """
              SELECT *
              FROM {}.agents
              WHERE host = %s AND agent_type = 'Open vSwitch agent'
            """.format(self.neutron_db),
            "vedge", host_id)
        host = self.inv.get_by_id(self.get_env(), host_id)
        if not host:
            self.log.error("unable to find host in inventory: %s", host_id)
            return []
        host_types = host["host_type"]
        if "Network" not in host_types and "Compute" not in host_types:
            return []
        vsctl_lines = self.run_fetch_lines("ovs-vsctl show", host["id"])
        ports = self.fetch_ports(host, vsctl_lines)
        for doc in results:
            doc["name"] = doc["host"] + "-OVS"
            doc["configurations"] = json.loads(doc["configurations"])
            doc["ports"] = ports
            doc["tunnel_ports"] = self.get_overlay_tunnels(doc, vsctl_lines)
        return results

    def fetch_ports(self, host, vsctl_lines):
        host_types = host["host_type"]
        if "Network" not in host_types and "Compute" not in host_types:
            return {}
        ports = self.fetch_ports_from_dpctl(host["id"])
        self.fetch_port_tags_from_vsctl(vsctl_lines, ports)
        return ports

    def fetch_ports_from_dpctl(self, host_id):
        cmd = "ovs-dpctl show"
        lines = self.run_fetch_lines(cmd, host_id)
        ports = {}
        for l in lines:
            port_matches = self.port_re.match(l)
            if not port_matches:
                continue
            port = {}
            port_id = port_matches.group(1)
            name = port_matches.group(2)
            is_internal = port_matches.group(3) == " (internal)"
            port["internal"] = is_internal
            port["id"] = port_id
            port["name"] = name
            ports[name] = port
        return ports

    # from ovs-vsctl, fetch tags of ports
    # example format of ovs-vsctl output for a specific port:
    #        Port "tap9f94d28e-7b"
    #            tag: 5
    #            Interface "tap9f94d28e-7b"
    #                type: internal
    def fetch_port_tags_from_vsctl(self, vsctl_lines, ports):
        port = None
        for l in vsctl_lines:
            if l.startswith(self.port_line_header_prefix):
                port = None
                port_name = l[len(self.port_line_header_prefix):]
                # remove quotes from port name
                if '"' in port_name:
                    port_name = port_name[1:][:-1]
                if port_name in ports:
                    port = ports[port_name]
                continue
            if not port:
                continue
            if l.startswith(" " * 12 + "tag: "):
                port["tag"] = l[l.index(":") + 2:]
                ports[port["name"]] = port
        return ports

    def get_overlay_tunnels(self, doc, vsctl_lines):
        if doc["agent_type"] != "Open vSwitch agent":
            return {}
        if "tunneling_ip" not in doc["configurations"]:
            return {}
        if not doc["configurations"]["tunneling_ip"]:
            self.get_pnics(doc)
            return {}

        # read the 'br-tun' interface ports
        # this will be used later in the OTEP
        tunnel_bridge_header = " " * 4 + "Bridge br-tun"
        try:
            br_tun_loc = vsctl_lines.index(tunnel_bridge_header)
        except ValueError:
            return []
        lines = vsctl_lines[br_tun_loc + 1:]
        tunnel_ports = {}
        port = None
        for l in lines:
            # if we have only 4 or less spaces in the beginng,
            # the br-tun section ended so return
            if not l.startswith(" " * 5):
                break
            if l.startswith(self.port_line_header_prefix):
                if port:
                    tunnel_ports[port["name"]] = port
                name = l[len(self.port_line_header_prefix):].strip('" ')
                port = {"name": name}
            elif port and l.startswith(" " * 12 + "Interface "):
                interface = l[10 + len("Interface ") + 1:].strip('" ')
                port["interface"] = interface
            elif port and l.startswith(" " * 16):
                colon_pos = l.index(":")
                attr = l[:colon_pos].strip()
                val = l[colon_pos + 2:].strip('" ')
                if attr == "options":
                    opts = val.strip('{}')
                    val = {}
                    for opt in opts.split(", "):
                        opt_name = opt[:opt.index("=")]
                        opt_val = opt[opt.index("=") + 1:].strip('" ')
                        val[opt_name] = opt_val
                port[attr] = val
        if port:
            tunnel_ports[port["name"]] = port
        return tunnel_ports

    def get_pnics(self, vedge) -> dict:
        bridges = vedge["configurations"].get("bridge_mappings", {})
        pnics = {}
        for bridge in bridges.values():
            self.get_bridge_pnic(pnics, vedge, bridge)
        return pnics

    MIRANTIS_DIST = "Mirantis"

    def get_bridge_pnic(self, pnics: dict, vedge: dict, bridge: dict):
        cmd = "ovs-vsctl list-ifaces {}".format(bridge)
        ifaces_list_lines = self.run_fetch_lines(cmd, vedge["host"])
        env_config = self.configuration.get_env_config()
        distribution = env_config.get("distribution")
        dist_version = env_config.get("distribution_version")
        use_br_postfix = distribution == self.MIRANTIS_DIST and \
            dist_version in ["6.0", "7.0", "8.0"]
        for l in ifaces_list_lines:
            if use_br_postfix:
                br_pnic_postfix = "{}--br-".format(bridge)
                interface = l[len(br_pnic_postfix):] \
                    if l.startswith(br_pnic_postfix) \
                    else ""
            else:
                interface = l
            if interface:
                pnic = self.find_pnic_for_interface(vedge, interface)
                if pnic:
                    pnics[pnic["name"]] = pnic

    def find_pnic_for_interface(self, vedge, interface):
        # add port ID to pNIC
        pnic = self.inv.find_items({
            "environment": self.get_env(),
            "type": "host_pnic",
            "host": vedge["host"],
            "name": interface
        }, get_single=True)
        if not pnic:
            return
        vedge["pnic"] = interface
        port = vedge["ports"].get(interface, {})
        pnic["port_id"] = port.get("id", "")
        self.inv.set(pnic)
        return pnic
Exemplo n.º 3
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.input_text = None

    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 = ['vedge', 'vservice']
        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 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']

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

    def process_check_result(self):
        self.read_input()
        self.process_input()
Exemplo n.º 4
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()
Exemplo n.º 5
0
class StatsConsumer(MongoAccess):
    default_env = "WebEX-Mirantis@Cisco"

    def __init__(self):
        self.get_args()
        MongoAccess.set_config_file(self.args.mongo_config)
        MongoAccess.__init__(self)
        self.log = FullLogger()
        self.log.set_loglevel(self.args.loglevel)
        self.conf = Configuration()
        self.inv = InventoryMgr()
        self.inv.set_collections(self.args.inventory)
        stats_coll = self.inv.get_coll_name('statistics')
        self.stats = self.db[stats_coll]
        # consume messages from topic
        self.consumer = KafkaConsumer('VPP.stats',
                                      group_id='calipso_test',
                                      auto_offset_reset=self.args.offset,
                                      bootstrap_servers=['localhost:9092'])

    def get_args(self):
        # try to read scan plan from command line parameters
        parser = argparse.ArgumentParser()
        parser.add_argument("-m", "--mongo_config", nargs="?", type=str,
                            default="",
                            help="name of config file " +
                            "with MongoDB servr access details")
        parser.add_argument("-e", "--env", nargs="?", type=str,
                            default=self.default_env,
                            help="name of environment to scan \n" +
                            "(default: " + self.default_env + ")")
        parser.add_argument("-y", "--inventory", nargs="?", type=str,
                            default="inventory",
                            help="name of inventory collection \n" +
                            "(default: 'inventory')")
        parser.add_argument("-l", "--loglevel", nargs="?", type=str,
                            default="INFO",
                            help="logging level \n(default: 'INFO')")
        parser.add_argument("-o", "--offset", nargs="?", type=str,
                            default="largest",
                            help="where to start reading" +
                                 " - use 'smallest' for start \n" +
                                 "(default: 'largest')")
        self.args = parser.parse_args()

    def read(self):
        for kafka_msg in self.consumer:
            msg = json.loads(kafka_msg.value.decode())
            self.add_stats(msg)

    def add_stats(self, msg):
        host_ip = msg['hostIp']
        search = {
            'environment': self.args.env,
            'type': 'host',
            'ip_address': host_ip
        }
        host = self.inv.find_items(search, get_single=True)
        if not host:
            self.log.error('could not find host with ip address=' + host_ip)
            return
        host_id = host['id']
        search = {
            'environment': self.args.env,
            'type': 'vedge',
            'host': host_id
        }
        vedge = self.inv.find_items(search, get_single=True)
        if not vedge:
            self.log.error('could not find vEdge for host: ' + host_id)
            return
        self.log.info('setting VPP stats for vEdge of host: ' + host_id)
        self.add_stats_for_object(vedge, msg)

    def add_stats_for_object(self, o, msg):
        msg['type'] = 'vedge_flows'
        msg['environment'] = self.args.env
        msg['object_type'] = o['type']
        msg['object_id'] = o['id']
        time_seconds = int(msg['averageArrivalNanoSeconds'] / 1000000000)
        sample_time = time.gmtime(time_seconds)
        msg['sample_time'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", sample_time)
        # find instances between which the flow happens
        # to find the instance, find the related vNIC first
        msg['source'] = self.find_instance_for_stat('source', msg)
        msg['destination'] = self.find_instance_for_stat('destination', msg)
        self.stats.insert_one(msg)

    def find_instance_for_stat(self, direction, msg):
        search_by_mac_address = 'sourceMacAddress' in msg
        value_attr = 'MacAddress' if search_by_mac_address else 'IpAddress'
        value_to_search = msg[direction + value_attr]
        attr = 'mac_address' if search_by_mac_address else 'ip_address'
        search = {
            'environment': self.args.env,
            'type': 'vnic',
            attr: value_to_search
        }
        vnic = self.inv.find_items(search, get_single=True)
        if not vnic:
            self.log.error('failed to find vNIC for ' +
                           attr + '=' + value_to_search)
            return 'Unknown'
        # now find the instance name from the vnic name
        name_path = vnic['name_path'].split('/')
        instance_name = name_path[8]
        return instance_name