def _assert_mtu_match(self, **kwargs) -> pd.DataFrame: """Workhorse routine that validates MTU match for specified input""" columns = kwargs.pop('columns', []) columns = [ "namespace", "hostname", "ifname", "state", "type", "mtu", "timestamp" ] type = kwargs.pop('type', 'ethernet') if_df = self.get(columns=columns, type=type, **kwargs) if if_df.empty: return pd.DataFrame(columns=columns + ["assert"]) lldpobj = LldpObj(context=self.ctxt) lldp_df = lldpobj.get(**kwargs).query('peerIfname != "-"') if lldp_df.empty: if_df['assertReason'] = 'No LLDP peering info' if_df['assert'] = 'fail' return if_df # Now create a single DF where you get the MTU for the lldp # combo of (namespace, hostname, ifname) and the MTU for # the combo of (namespace, peerHostname, peerIfname) and then # pare down the result to the rows where the two MTUs don't match combined_df = (pd.merge( lldp_df, if_df[["namespace", "hostname", "ifname", "mtu"]], on=["namespace", "hostname", "ifname"], how="outer", ).dropna(how="any").merge( if_df[["namespace", "hostname", "ifname", "mtu"]], left_on=["namespace", "peerHostname", "peerIfname"], right_on=["namespace", "hostname", "ifname"], how="outer", ).dropna(how="any").drop( columns=["hostname_y", "ifname_y", "mgmtIP", "description" ]).rename( index=str, columns={ "hostname_x": "hostname", "ifname_x": "ifname", "mtu_x": "mtu", "mtu_y": "peerMtu", }, )) if combined_df.empty: if_df['assertReason'] = 'No LLDP peering info' if_df['assert'] = 'fail' return if_df combined_df['assert'] = combined_df.apply( lambda x: 'pass' if x['mtu'] == x['peerMtu'] else 'fail', axis=1) return combined_df
def _assert_mtu_match(self, **kwargs) -> pd.DataFrame: """Workhorse routine that validates MTU match for specified input""" lldpobj = LldpObj(context=self.ctxt) lldp_df = lldpobj.get(**kwargs) if lldp_df.empty: raise NoLLdpError("No Valid LLDP info found") columns = [ "namespace", "hostname", "ifname", "state", "mtu", "timestamp" ] if_df = self.get_valid_df( "interfaces", self.sort_fields, hostname=kwargs.get("hostname", []), namespace=kwargs.get("namespace", []), columns=columns, ifname=kwargs.get("ifname", []), ) if if_df.empty: return pd.DataFrame(columns=columns + ["assert"]) # Now create a single DF where you get the MTU for the lldp # combo of (namespace, hostname, ifname) and the MTU for # the combo of (namespace, peerHostname, peerIfname) and then # pare down the result to the rows where the two MTUs don't match combined_df = (pd.merge( lldp_df, if_df[["namespace", "hostname", "ifname", "mtu"]], on=["namespace", "hostname", "ifname"], how="outer", ).dropna(how="any").merge( if_df[["namespace", "hostname", "ifname", "mtu"]], left_on=["namespace", "peerHostname", "peerIfname"], right_on=["namespace", "hostname", "ifname"], how="outer", ).dropna(how="any").drop( columns=["hostname_y", "ifname_y", "mgmtIP", "description" ]).rename( index=str, columns={ "hostname_x": "hostname", "ifname_x": "ifname", "mtu_x": "mtu", "mtu_y": "peerMtu", }, )) if combined_df.empty: return combined_df combined_df['assert'] = combined_df.apply( lambda x: 'pass' if x['mtu'] == x['peerMtu'] else 'fail', axis=1) return combined_df
def aver(self, **kwargs) -> pd.DataFrame: """BGP Assert""" assert_cols = [ "namespace", "hostname", "vrf", "peer", "asn", "state", "peerAsn", "v4Enabled", "v6Enabled", "evpnEnabled", "v4Advertised", "v6Advertised", "evpnAdvertised", "v4Received", "v6Received", "evpnReceived", "bfdStatus", "reason", "notifcnReason" ] kwargs.pop("columns", None) # Loose whatever's passed df = self.get(columns=assert_cols, **kwargs) if df.empty: return pd.DataFrame() if_df = IfObj(context=self.ctxt).get(namespace=kwargs.get("namespace", ""), columns=['namespace', 'hostname', 'ifname', 'state']) \ .rename(columns={'state': 'ifState'}) lldp_df = LldpObj(context=self.ctxt).get( namespace=kwargs.get("namespace", ""), columns=[ 'namespace', 'hostname', 'ifname', 'peerHostname', 'peerIfname' ]) # Get the dataframes we need for processing df['cif'] = df.apply(self._get_connect_if, axis=1) df['ifname'] = df['cif'].str.split('.').str[0] df = df.merge(if_df, left_on=['namespace', 'hostname', 'cif'], right_on=['namespace', 'hostname', 'ifname'], how='left') \ .drop(columns=['timestamp_y', 'ifname_y']) \ .rename(columns={'ifname_x': 'ifname'}) # We split off at this point to avoid merging mess because of lack of # LLDP info df = df.merge(lldp_df, on=['namespace', 'hostname', 'ifname'], how='left') \ .drop(columns=['timestamp']) \ .rename(columns={'timestamp_x': 'timestamp'}) # Some munging to handle subinterfaces df['xx'] = df['peerIfname'] + '.' + df['cif'].str.split('.').str[1] df['peerIfname'] = df['xx'].where(df['xx'].notnull(), df['peerIfname']) df.drop(columns=['xx'], inplace=True) df = df.merge(df, left_on=['namespace', 'hostname', 'cif'], right_on=['namespace', 'peerHostname', 'peerIfname'], how='left') \ .drop(columns=['peerIfname_y', 'timestamp_y', 'cif_y', 'ifState_y', 'reason_y', 'notifcnReason_y']) \ .rename(columns={'timestamp_x': 'timestamp', 'cif_x': 'cif', 'ifState_x': 'ifState', 'reason_x': 'reason', 'notifcnReason_x': 'notifcnReason'}) df['peer_y'] = df['peer_y'].astype(str) \ .where(df['peer_y'].notnull(), '') df["assertReason"] = [[] for _ in range(len(df))] # Now all sessions with NaN in the oif column have no connected route df['assertReason'] += df.apply( lambda x: ["outgoing link down"] if x['cif'] and x['ifState'] != "up" else [], axis=1) df['assertReason'] += df.apply(lambda x: ["no route to peer"] if not len(x['cif']) else [], axis=1) df['assertReason'] += df.apply(self._check_afi_safi, axis=1) df['assertReason'] += df.apply( lambda x: ["asn mismatch"] if x['peer_y'] and ((x["asn_x"] != x["peerAsn_y"]) or (x['asn_y'] != x['peerAsn_x'])) else [], axis=1) df['assertReason'] += df.apply( lambda x: [f"{x['reason']}:{x['notifcnReason']}"] if (x['reason'] and x['state_x'] != 'Established') else [], axis=1) df['assert'] = df.apply(lambda x: 'pass' if not len(x.assertReason) else 'fail', axis=1) return (df[[ 'namespace', 'hostname_x', 'vrf_x', 'peer_x', 'asn_x', 'peerAsn_x', 'state_x', 'peerHostname_x', 'vrf_y', 'peer_y', 'asn_y', 'peerAsn_y', 'assert', 'assertReason', 'timestamp' ]].rename(columns={ 'hostname_x': 'hostname', 'vrf_x': 'vrf', 'peer_x': 'peer', 'asn_x': 'asn', 'peerAsn_x': 'peerAsn', 'state_x': 'state', 'peerHostname_x': 'hostnamePeer', 'vrf_y': 'vrfPeer', 'peer_y': 'peerPeer', 'asn_y': 'asnPeer', 'peerAsn_y': 'peerAsnPeer' }, copy=False).explode(column="assertReason").astype( { 'asn': 'Int64', 'peerAsn': 'Int64', 'asnPeer': 'Int64', 'peerAsnPeer': 'Int64' }, copy=False).fillna({'assertReason': '-'}))
def aver(self, **kwargs): """Assert that the OSPF state is OK""" columns = [ "namespace", "hostname", "vrf", "ifname", "routerId", "helloTime", "deadTime", "passive", "ipAddress", "isUnnumbered", "areaStub", "networkType", "timestamp", "area", "nbrCount", "origIfname", ] sort_fields = ["namespace", "hostname", "ifname", "vrf"] ospf_df = self.get_valid_df("ospfIf", sort_fields, columns=columns, **kwargs) if ospf_df.empty: return pd.DataFrame(columns=columns) ospf_df['ifname'] = ospf_df['origIfname'] ospf_df.drop(columns=['origIfname'], inplace=True) ospf_df["assertReason"] = [[] for _ in range(len(ospf_df))] df = (ospf_df[ospf_df["routerId"] != ""].groupby( ["routerId", "namespace"], as_index=False)[[ "hostname", "namespace" ]].agg(lambda x: x.unique().tolist())).dropna(how='any') # df is a dataframe with each row containing the routerId and the # corresponding list of hostnames with that routerId. In a good # configuration, the list must have exactly one entry ospf_df['assertReason'] = (ospf_df.merge( df, on=["routerId"], how="outer").apply( lambda x: ["duplicate routerId {}".format(x["hostname_y"])] if len(x['hostname_y']) != 1 else [], axis=1)) # Now peering match lldpobj = LldpObj(context=self.ctxt) lldp_df = lldpobj.get(namespace=kwargs.get("namespace", ""), hostname=kwargs.get("hostname", ""), ifname=kwargs.get("ifname", ""), columns=[ "namespace", "hostname", "ifname", "peerHostname", "peerIfname", "peerMacaddr" ]) if lldp_df.empty: ospf_df['assertReason'] = 'No LLDP peering info' ospf_df['assert'] = 'fail' return ospf_df[[ 'namespace', 'hostname', 'vrf', 'ifname', 'assertReason', 'assert' ]] # Create a single massive DF with fields populated appropriately use_cols = [ "namespace", "routerId", "hostname", "vrf", "ifname", "helloTime", "deadTime", "passive", "ipAddress", "areaStub", "isUnnumbered", "networkType", "area", "timestamp", ] int_df = ospf_df[use_cols].merge(lldp_df, on=["namespace", "hostname", "ifname"]) \ .dropna(how="any") if int_df.empty: ospf_df['assertReason'] = 'No LLDP peering info' ospf_df['assert'] = 'fail' return ospf_df[[ 'namespace', 'hostname', 'vrf', 'ifname', 'assertReason', 'assert' ]] ospf_df = ospf_df.merge(int_df, left_on=["namespace", "hostname", "ifname"], right_on=["namespace", "peerHostname", "peerIfname"]) \ .dropna(how="any") # Now start comparing the various parameters ospf_df["assertReason"] += ospf_df.apply( lambda x: ["subnet mismatch"] if ((x["isUnnumbered_x"] != x["isUnnumbered_y"]) and (IPv4Network(x["ipAddress_x"], strict=False) != IPv4Network( x["ipAddress_y"], strict=False))) else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["area mismatch"] if (x["area_x"] != x["area_y"] and x[ "areaStub_x"] != x["areaStub_y"]) else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["Hello timers mismatch"] if x["helloTime_x"] != x["helloTime_y"] else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["Dead timer mismatch"] if x["deadTime_x"] != x["deadTime_y"] else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["network type mismatch"] if x["networkType_x"] != x["networkType_y"] else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["passive config mismatch"] if x["passive_x"] != x["passive_y"] else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["vrf mismatch"] if x["vrf_x"] != x["vrf_y"] else [], axis=1, ) # Fill up a single assert column now indicating pass/fail ospf_df['assert'] = ospf_df.apply( lambda x: 'pass' if not len(x['assertReason']) else 'fail', axis=1) return (ospf_df.rename( index=str, columns={ "hostname_x": "hostname", "ifname_x": "ifname", "vrf_x": "vrf", }, )[[ "namespace", "hostname", "ifname", "vrf", "assert", "assertReason", "timestamp" ]].explode(column='assertReason').fillna({'assertReason': '-'}))
def _assert_interfaces(self, **kwargs) -> pd.DataFrame: """Workhorse routine that validates MTU match for specified input""" columns = kwargs.pop('columns', []) status = kwargs.pop('status', 'all') stime = kwargs.pop('start_time', '') etime = kwargs.pop('end_time', '') columns = [ "namespace", "hostname", "ifname", "state", "type", "mtu", "vlan", "adminState", "ipAddressList", "ip6AddressList", "speed", "master", "timestamp" ] if_df = self.get(columns=columns, **kwargs) if if_df.empty: if status != 'pass': if_df['assert'] = 'fail' if_df['assertReason'] = 'No data' return if_df lldpobj = LldpObj(context=self.ctxt, start_time=stime, end_time=etime) vlanobj = VlanObj(context=self.ctxt, start_time=stime, end_time=etime) # can't pass all kwargs, because lldp acceptable arguements are # different than interface namespace = kwargs.get('namespace', None) hostname = kwargs.get('hostname', None) lldp_df = lldpobj.get(namespace=namespace, hostname=hostname) \ .query('peerIfname != "-"') # Get the VLAN info to ensure trunking ports are identical on both ends vlan_df = vlanobj.get(namespace=namespace, hostname=hostname) if not vlan_df.empty: vlan_df = vlan_df.explode('interfaces') \ .drop(columns=['vlanName'], errors='ignore') \ .rename(columns={'interfaces': 'ifname', 'vlan': 'vlanList'}) vlan_df = vlan_df.groupby( by=['namespace', 'hostname', 'ifname' ])['vlanList'].unique().reset_index().query('ifname != ""') # OK merge this list into the interface info if_df = if_df.merge(vlan_df, on=['namespace', 'hostname', 'ifname'], how='outer') if_df['vlanList'] = if_df['vlanList'] \ .fillna({i: [] for i in if_df.index}) if lldp_df.empty: if status != 'pass': if_df['assertReason'] = 'No LLDP peering info' if_df['assert'] = 'fail' return if_df # Now create a single DF where you get the MTU for the lldp # combo of (namespace, hostname, ifname) and the MTU for # the combo of (namespace, peerHostname, peerIfname) and then # pare down the result to the rows where the two MTUs don't match combined_df = (pd.merge( lldp_df, if_df, on=["namespace", "hostname", "ifname"], how="outer", ).dropna(how="any").merge( if_df, left_on=["namespace", "peerHostname", "peerIfname"], right_on=["namespace", "hostname", "ifname"], how="outer", suffixes=["", "Peer"], ).dropna(how="any").drop(columns=[ "hostnamePeer", "ifnamePeer", "timestamp_x", "mgmtIP", "description" ])) if combined_df.empty: if status != 'pass': if_df['assertReason'] = 'No LLDP peering info' if_df['assert'] = 'fail' return if_df combined_df['assertReason'] = combined_df.apply( lambda x: [] if (x['adminState'] == 'down' or (x['adminState'] == "up" and x[ 'state'] == "up")) else ['Interface down'], axis=1) combined_df['assertReason'] += combined_df.apply( lambda x: [] if x['mtu'] == x['mtuPeer'] else ['MTU mismatch'], axis=1) combined_df['assertReason'] += combined_df.apply( lambda x: [] if x['vlan'] == x['vlanPeer'] else ['PVID mismatch'], axis=1) combined_df['assertReason'] += combined_df.apply( lambda x: [] if x['speed'] == x['speedPeer'] else ['Speed mismatch'], axis=1) combined_df['assertReason'] += combined_df.apply(lambda x: [] if ( (x['type'] == x['typePeer']) or (x['type'] == 'vlan' and x['typePeer'] == 'subinterface') or (x['type'].startswith('ether') and x['typePeer'].startswith( 'ether'))) else ['type mismatch'], axis=1) combined_df['assertReason'] += combined_df.apply(lambda x: [] if ( (len(x['ipAddressList']) == len(x['ipAddressListPeer'])) and ( (len(x['ipAddressList']) == 0) or (x['ipAddressList'][0].split('/')[1] == '32') or (ip_network(x['ipAddressList'][0], strict=False) == ip_network( x['ipAddressListPeer'][0], strict=False) ))) else ['IP address mismatch'], axis=1) combined_df['assertReason'] += combined_df.apply(lambda x: [] if set(x[ 'vlanList']) == set(x['vlanListPeer']) else ['VLAN set mismatch'], axis=1) combined_df['assert'] = combined_df.apply( lambda x: 'fail' if len(x.assertReason) else 'pass', axis=1) if status == "fail": combined_df = combined_df.query('assertReason.str.len() != 0') elif status == "pass": combined_df = combined_df.query('assertReason.str.len() == 0') return combined_df[[ 'namespace', 'hostname', 'ifname', 'state', 'peerHostname', 'peerIfname', 'assert', 'assertReason' ]]
def aver(self, **kwargs): """Assert that the OSPF state is OK""" kwargs.pop('columns', []) columns = [ "namespace", "hostname", "vrf", "ifname", "state", "routerId", "helloTime", "deadTime", "passive", "ipAddress", "isUnnumbered", "areaStub", "networkType", "timestamp", "area", "nbrCount", ] status = kwargs.pop('status', 'all') # we have to not filter hostname at this point because we need to # understand neighbor relationships ospf_df = self.get_valid_df("ospfIf", columns=columns, state="!adminDown", **kwargs) if ospf_df.empty: return pd.DataFrame(columns=columns) ospf_df["assertReason"] = [[] for _ in range(len(ospf_df))] df = ( ospf_df[ospf_df["routerId"] != ""] .groupby(["routerId", "namespace"], as_index=False)[["hostname", "namespace"]] .agg(lambda x: x.unique().tolist()) ).dropna(how='any') # df is a dataframe with each row containing the routerId and the # corresponding list of hostnames with that routerId. In a good # configuration, the list must have exactly one entry. if not df.empty: # This check is because we don't get routerID with Arista boxes ospf_df['assertReason'] = ( ospf_df.merge(df, on=["routerId"], how="outer") .apply(lambda x: ["duplicate routerId {}".format( x["hostname_y"])] if len(x['hostname_y']) != 1 else [], axis=1)) # Now peering match lldpobj = LldpObj(context=self.ctxt) lldp_df = lldpobj.get( namespace=kwargs.get("namespace", ""), hostname=kwargs.get("hostname", ""), ifname=kwargs.get("ifname", ""), columns=["namespace", "hostname", "ifname", "peerHostname", "peerIfname", "peerMacaddr"] ) if lldp_df.empty: ospf_df = ospf_df[~(ospf_df.ifname.str.contains('loopback') | ospf_df.ifname.str.contains('Vlan'))] ospf_df['assertReason'] = 'No LLDP peering info' ospf_df['assert'] = 'fail' return ospf_df[['namespace', 'hostname', 'vrf', 'ifname', 'assertReason', 'assert']] # Create a single massive DF with fields populated appropriately use_cols = [ "namespace", "routerId", "hostname", "vrf", "ifname", "helloTime", "deadTime", "passive", "ipAddress", "areaStub", "isUnnumbered", "networkType", "area", "timestamp", ] int_df = ospf_df[use_cols].merge(lldp_df, on=["namespace", "hostname", "ifname"]) \ .dropna(how="any") if int_df.empty: # Weed out the loopback and SVI interfaces as they have no LLDP peers if status == "pass": ospf_df = ospf_df[(ospf_df.ifname.str.contains('loopback') | ospf_df.ifname.str.contains('Vlan'))] ospf_df['assertReason'] = [] ospf_df['assert'] = 'pass' else: ospf_df = ospf_df[~(ospf_df.ifname.str.contains('loopback') | ospf_df.ifname.str.contains('Vlan'))] ospf_df['assertReason'] = 'No LLDP peering info' ospf_df['assert'] = 'fail' return ospf_df[['namespace', 'hostname', 'vrf', 'ifname', 'assertReason', 'assert']] peer_df = ospf_df.merge(int_df, left_on=["namespace", "hostname", "ifname"], right_on=["namespace", "peerHostname", "peerIfname"]) \ .dropna(how="any") if peer_df.empty: ospf_df = ospf_df[~(ospf_df.ifname.str.contains('loopback') | ospf_df.ifname.str.contains('Vlan'))] ospf_df['assertReason'] = 'No LLDP peering info' ospf_df['assert'] = 'fail' else: ospf_df = peer_df # Now start comparing the various parameters ospf_df["assertReason"] += ospf_df.apply( lambda x: ["subnet mismatch"] if ( (x["isUnnumbered_x"] != x["isUnnumbered_y"]) and ( IPv4Network(x["ipAddress_x"], strict=False) != IPv4Network(x["ipAddress_y"], strict=False) ) ) else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["area mismatch"] if (x["area_x"] != x["area_y"] and x["areaStub_x"] != x["areaStub_y"]) else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["Hello timers mismatch"] if x["helloTime_x"] != x["helloTime_y"] else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["Dead timer mismatch"] if x["deadTime_x"] != x["deadTime_y"] else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["network type mismatch"] if x["networkType_x"] != x["networkType_y"] else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["passive config mismatch"] if x["passive_x"] != x["passive_y"] else [], axis=1, ) ospf_df["assertReason"] += ospf_df.apply( lambda x: ["vrf mismatch"] if x["vrf_x"] != x["vrf_y"] else [], axis=1, ) # Fill up a single assert column now indicating pass/fail ospf_df['assert'] = ospf_df.apply(lambda x: 'pass' if not len(x['assertReason']) else 'fail', axis=1) result = ( ospf_df.rename( index=str, columns={ "hostname_x": "hostname", "ifname_x": "ifname", "vrf_x": "vrf", }, )[["namespace", "hostname", "ifname", "vrf", "assert", "assertReason", "timestamp"]].explode(column='assertReason') .fillna({'assertReason': '-'}) ) if status == "pass": return result.query('assertReason == "-"') elif status == "fail": return result.query('assertReason != "-"') return result