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
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
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()
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()
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