Example #1
0
 def _get_connect_if(self, row) -> str:
     """Given a BGP DF row, retrieve the connecting interface for the row"""
     if re.match(r'^[0-9a-f.:]*$', row['peer']):
         rslt = RoutesObj(context=self.ctxt).lpm(namespace=row['namespace'],
                                                 hostname=row['hostname'],
                                                 address=row['peer'],
                                                 vrf=row['vrf'])
         if not rslt.empty:
             val = rslt['oifs'][0][0]
         else:
             val = ''
     else:
         val = row['peer']
     return val
Example #2
0
    def _is_vtep_reachable(self, row):
        reason = []
        defrt = ipaddress.IPv4Network("0.0.0.0/0")
        for vtep in row['remoteVtepList'].tolist():
            rdf = RoutesObj(context=self.ctxt) \
                .lpm(namespace=row['namespace'], vrf=['default'],
                     hostname=row['hostname'], address=vtep)
            if rdf.empty:
                reason += [f"{vtep} not reachable"]
                continue
            if rdf.prefix[0] == defrt:
                reason += [f"{vtep} reachable via default"]

        return reason
Example #3
0
    def _is_vtep_reachable(self, row):
        reason = []
        defrt = ipaddress.IPv4Network("0.0.0.0/0")
        for vtep in row['remoteVtepList'].tolist():
            if vtep == '-':
                continue

            # Have to hardcode lpm query here sadly to avoid reading of the
            # data repeatedly. The time for asserting the state of 500 VNIs
            # came down from 194s to 4s with the below piece of code instead
            # of invoking route's lpm to accomplish the task.
            cached_df = self._routes_df \
                            .query(f'namespace=="{row.namespace}" and hostname=="{row.hostname}"')
            route = RoutesObj(context=self.ctxt).lpm(vrf='default',
                                                     address=vtep,
                                                     cached_df=cached_df)

            if route.empty:
                reason += [f"{vtep} not reachable"]
                continue
            if route.prefix.tolist() == [defrt]:
                reason += [f"{vtep} reachable via default"]

        return reason
Example #4
0
    def aver(self, **kwargs) -> pd.DataFrame:
        """Assert for EVPN Data"""

        assert_cols = [
            "namespace", "hostname", "vni", "remoteVtepList", "vrf",
            "mcastGroup", "type", "priVtepIp", "state", "l2VniList", "ifname",
            "secVtepIp"
        ]

        kwargs.pop("columns", None)  # Loose whatever's passed
        status = kwargs.pop('status', 'all')

        df = self.get(columns=assert_cols, **kwargs)
        if df.empty:
            df = pd.DataFrame(columns=assert_cols)
            if status != 'pass':
                df['assertReason'] = 'No data found'
                df['assert'] = 'fail'
            return df

        df["assertReason"] = [[] for _ in range(len(df))]

        # Gather the unique set of VTEPs per VNI
        vteps_df = df.explode(column='remoteVtepList') \
            .dropna(how='any') \
            .groupby(by=['vni', 'type'])['remoteVtepList'] \
            .aggregate(lambda x: x.unique().tolist()) \
            .reset_index() \
            .dropna(how='any') \
            .rename(columns={'remoteVtepList': 'allVteps'})

        if not vteps_df.empty:
            her_df = df.merge(vteps_df)
        else:
            her_df = pd.DataFrame()

        if (not her_df.empty and (her_df.remoteVtepList.str.len() != 0).any()):
            # Check if every VTEP we know is reachable
            rdf = RoutesObj(context=self.ctxt).get(
                namespace=kwargs.get('namespace'), vrf='default')
            if not rdf.empty:
                rdf['prefixlen'] = rdf['prefix'].str.split('/') \
                                                    .str[1].astype('int')
            self._routes_df = rdf

            her_df["assertReason"] += her_df.apply(self._is_vtep_reachable,
                                                   axis=1)

        mcast_df = df.query('mcastGroup != "0.0.0.0"')
        if not mcast_df.empty:
            # Ensure that all VNIs have at most one multicast group associated
            # per namespace

            mismatched_vni_df = mcast_df \
                .groupby(by=['namespace', 'vni'])['mcastGroup'] \
                .unique() \
                .reset_index() \
                .dropna() \
                .query('mcastGroup.str.len() != 1')

            if not mismatched_vni_df.empty:
                df['assertReason'] += df.apply(
                    lambda x, err_df: ['VNI has multiple mcast group']
                    if (x['namespace'] in err_df['namespace'] and x['vni'] in
                        err_df['vni']) else [],
                    axis=1,
                    args=(mismatched_vni_df, ))
        elif not her_df.empty:
            # Every VTEP has info about every other VTEP for a given VNI
            her_df["assertReason"] += her_df.apply(self._all_vteps_present,
                                                   axis=1)

        # State is up
        df["assertReason"] += df.apply(
            lambda x: ['interface is down']
            if x['type'] == "L2" and x['state'] != "up" else [],
            axis=1)

        devices = df["hostname"].unique().tolist()
        ifdf = IfObj(context=self.ctxt) \
            .get(namespace=kwargs.get("namespace", ""), hostname=devices,
                 type='vxlan')

        df = df.merge(
            ifdf[['namespace', 'hostname', 'ifname', 'master', 'vlan']],
            on=['namespace', 'hostname', 'ifname'],
            how='left')

        # vxlan interfaces, if defined, for every VNI is part of bridge
        # We ensure this is true artificially for NXOS, ignored for JunOS.
        df["assertReason"] += df.apply(
            lambda x: ['vni not in bridge'] if (x['type'] == "L2" and x[
                'ifname'] != '-' and x['master'] != "bridge") else [],
            axis=1)

        # mac_df = MacsObj(context=self.ctxt) \
        #     .get(namespace=kwargs.get("namespace", ""),
        #          macaddr=["00:00:00:00:00:00"])

        # # Assert that we have HER for every remote VTEP
        # df['assertReason'] += df.apply(self._is_her_good,
        #                                args=(mac_df, ), axis=1)

        # Fill out the assert column
        df['assert'] = df.apply(lambda x: 'pass'
                                if not len(x.assertReason) else 'fail',
                                axis=1)

        if status == 'fail':
            df = df.query('assertReason.str.len() != 0')
        elif status == "pass":
            df = df.query('assertReason.str.len() == 0')

        return df[['namespace', 'hostname', 'vni', 'type',
                   'assertReason', 'assert', 'timestamp']] \
            .explode(column='assertReason') \
            .fillna({'assertReason': '-'})