#!/usr/bin/env python
from cStringIO import StringIO
from ipaddr import IPNetwork
from pprint import pformat, pprint
from subprocess import Popen, PIPE
import argparse
import json
import os
import re
import sys
import cumulus.platforms
def rfc5952(dest):
    dest = (":" + dest).replace(":0", ":").replace(":0", ":").replace(":0", ":")
    if dest.count(":0:0:0:0:0:0:0:0"):
        dest = dest.replace(":0:0:0:0:0:0:0:0", "::", 1)
    elif dest.count("::::::::"):
        dest = dest.replace("::::::::", "::", 1)
    elif dest.count(":0:0:0:0:0:0:0"):
        dest = dest.replace(":0:0:0:0:0:0:0", "::", 1)
    elif dest.count(":::::::"):
        dest = dest.replace(":::::::", "::", 1)
    elif dest.count(":0:0:0:0:0:0:"):
        dest = dest.replace(":0:0:0:0:0:0:", "::", 1)
    elif dest.count("::::::"):
        dest = dest.replace("::::::", "::", 1)
    elif dest.count(":0:0:0:0:0:"):
        dest = dest.replace(":0:0:0:0:0:", "::", 1)
    elif dest.count(":::::"):
        dest = dest.replace(":::::", "::", 1)
    elif dest.count(":0:0:0:0:"):
        dest = dest.replace(":0:0:0:0:", "::", 1)
    elif dest.count("::::"):
        dest = dest.replace("::::", "::", 1)
    elif dest.count(":0:0:0:"):
        dest = dest.replace(":0:0:0:", "::", 1)
    elif dest.count(":::"):
        dest = dest.replace(":::", "::", 1)
    elif dest.count(":0:0:"):
        dest = dest.replace(":0:0:", "::", 1)
    elif dest.count(":0:0/"):
        dest = dest.replace(":0:0/", "::/", 1)
    dest = dest.replace("::0", "::", 1)
    dest = dest[1:]
    return dest
class Kernel(object):
    def __init__(self):
        self.ifaces = {}             
        self.ipv6_local_routes = {}  
        self.neigh_macs = {}         
        self.neighs = {}             
        self.onlinks = {}
        self.routes = {}             
        self.vrfs = {}               
        self.neigh_exceptions = set()
        self.route_exceptions = set()
    def collect_data(self):
        def extract_table(line):
            table = 0
            re_table = re.search('table (\S+)', line)
            if re_table:
                table = re_table.group(1)
                if table == 'local':
                    table = 0
                elif table.isdigit():
                    table = int(table)
            if table in self.vrfs:
                table = self.vrfs.get(table)
            return table
        ipaddr = re.compile("""
         (\d+):\s+                                # ifindex
         ([^ ]+):                                 # iface
         .*link/(loopback|ether)                  # cruft
         \s([^ ]+)                                # mac addr
        """, re.VERBOSE)
        for line in Popen(["/sbin/ip", "-o", "addr"], shell=False, stdout=PIPE).stdout:
            parsed = ipaddr.findall(line)
            if parsed:
                ifindex, iface, cruft, mac = parsed[0]
                self.ifaces[iface] = (int(ifindex), mac)
        vrftable = re.compile("^\d+:\s+(\S+):.*vrf table (\d+)")
        for line in Popen(["/sbin/ip", "-d", "-o", "link", "show", "type", "vrf"], shell=False, stdout=PIPE).stdout:
            parsed = vrftable.findall(line)
            if parsed:
                (vrf_name, vrf_id) = parsed[0]
                vrf_id = int(vrf_id)
                self.vrfs[vrf_name] = vrf_id
        neigh_with_mac = re.compile("""
         ([^ ]+)                                     # prefix
         \sdev                                       # 'dev'
         \s([^ ]+)                                   # iface
         \slladdr                                    # 'lladdr'
         \s([^ ]+)                                   # mac addr
        """, re.VERBOSE)
        neigh_without_mac = re.compile("""
         ([^ ]+)                                     # prefix
         \sdev                                       # 'dev'
         \s([^ ]+)                                   # iface
         \s+([^ ]+)                                  # router or nud
        """, re.VERBOSE)
        for line in Popen(["/sbin/ip", "-o", "neigh"], shell=False, stdout=PIPE).stdout:
            parsed_with_mac = neigh_with_mac.findall(line)
            parsed_without_mac = neigh_without_mac.findall(line)
            if parsed_with_mac:
                prefix, iface, mac = parsed_with_mac[0]
                if iface.startswith('eth') and ignore_non_swps:
                    self.neigh_exceptions.update(set([prefix]))
                self.neighs[(prefix, iface)] = (iface, mac)
                self.neigh_macs.setdefault(mac, []).append(iface)
            elif parsed_without_mac:
                prefix, iface, nud = parsed_without_mac[0]
                if 'FAILED' in nud:
                    self.neigh_exceptions.update(set([prefix]))
                if iface.startswith('eth') and ignore_non_swps:
                    self.neigh_exceptions.update(set([prefix]))
                self.neighs[(prefix, iface)] = (iface, None)
        route = re.compile("(^default |^local |^broadcast |^) *([^ ]+)")
        self.missing_neighbors = []
        self.route_exceptions.update(set([(0, 'ff00::/8')]))
        for line in Popen(["/sbin/ip", "-o", "route", "show", "table", "all"],
                          shell=False, stdout=PIPE).stdout:
            begin, prefix = route.findall(line)[0]
            if begin == "default ":
                prefix = "0.0.0.0/0"
                iface = re.findall('dev (\w+)', line)[0]
                table = extract_table(line)
                vias = re.findall("via ([^ ]+) ", line)
                devs = re.findall("dev ([^ ]+)", line)
                add_exception = False
                for iface, via in zip(devs, vias):
                    if (via, iface) in self.neighs:
                        neigh_iface, neigh_mac = self.neighs[(via, iface)]
                        if iface == neigh_iface:
                            self.routes.setdefault((table, prefix), []).append((iface, neigh_mac))
                            if not iface.startswith('swp') and ignore_non_swps == True:
                                add_exception = True
                    else:
                        self.missing_neighbors.append((table, iface, via))
                        self.routes.setdefault((table, prefix), []).append((iface, 'no neighbor found'))
                if add_exception:
                    self.route_exceptions.update(set([(table, prefix)]))
                continue
            elif ' table 10 ' in line:
                continue
            elif begin == "local " or begin == "broadcast ":
                if IPNetwork(prefix).is_loopback:
                    continue
            elif begin is "":
                if prefix == "unreachable" or prefix == "none":
                    continue
            else:
                continue
            is_ipv6_local_route = False
            if prefix.count("/") == 0:       
                if prefix.count(".") > 0:    
                    prefix += "/32"
                else:                           
                    is_ipv6_local_route = True  
                    prefix += "/128"
            table = extract_table(line)
            vias = re.findall("via ([^ ]+) ", line)
            devs = re.findall("dev ([^ ]+)", line)
            for iface, via in zip(devs, vias):
                if iface == "lo":
                    if is_ipv6_local_route:
                        self.ipv6_local_routes.setdefault((table, prefix), []).append((iface, None))
                    else:
                        self.routes.setdefault((table, prefix), []).append((iface, None))
                    self.route_exceptions.update(set([(table, prefix)]))
                    self.routes.setdefault((table, prefix), []).append((iface, None))
                elif (via, iface) in self.neighs:
                    neigh_iface, neigh_mac = self.neighs[(via, iface)]
                    if iface == neigh_iface:
                        self.routes.setdefault((table, prefix), []).append((iface, neigh_mac))
                        if not iface.startswith('swp') and ignore_non_swps == True:
                            self.route_exceptions.update(set([(table, prefix)]))
                else:
                    self.missing_neighbors.append((table, iface, via))
                    self.routes.setdefault((table, prefix), []).append((iface, 'no neighbor found'))
                if via in prefix and iface != "lo":
                    self.onlinks.setdefault(prefix, []).append((iface, None))
            if devs and not vias:
                iface = devs[0]
                if not iface.startswith('swp') and ignore_non_swps:
                    self.route_exceptions.update(set([(table, prefix)]))
                self.routes.setdefault((table, prefix), []).append((iface, None))
        if self.missing_neighbors:
            self.missing_neighbors = sorted(list(set(self.missing_neighbors)))
class Asic(object):
    def __init__(self):
        self.ip4_routes   = {}  
        self.ip6_routes   = {}  
        self.route_2_rif  = {}  
        self.route_2_ecmp = {}  
        self.egresses     = {}  
        self.hosts        = {}  
        self.hostsipv6    = {}  
        self.hroutes      = {}  
        self.intfs        = {}  
        self.multipaths   = {}  
        self.egress_exceptions  = set()
        self.mp_exceptions      = set()
        self.hroutes_exceptions = set()
        self.chip_id_map = {}
        self.chip = self.get_chipname()
        self.split_ipv6_local = False
    def get_id_2_name(self, chip_id):
        try:
            return self.chip_id_map[chip_id]
        except KeyError:
            return 'Unknown(%s)' % chip_id
    def build_route_dict(self):
        for dest, rif in self.route_2_rif.items():
            if rif in self.egresses:
                intf, mac = self.egresses[rif]
                self.hroutes[dest] = [(intf, mac)]
        for dest, ecmp_id in self.route_2_ecmp.items():
            self.hroutes[dest] = []
            if ecmp_id in self.multipaths:
                for n in self.multipaths[ecmp_id]:
                    if n in self.egresses:
                        intf, mac = self.egresses[n]
                        self.hroutes.setdefault(dest, []).append((intf, mac))
class Broadcom(Asic):
    def __init__(self):
        import bcmshell
        self.bs = bcmshell.bcmshell(keepopen=True, timeout=180)
        Asic.__init__(self)
        self.egress_exceptions = set(["100000", "100002"])
        kernel_neighbor_ipv6_in_host_ipv6 = ('Hurricane2',)
        if self.chip in kernel_neighbor_ipv6_in_host_ipv6:
            self.split_ipv6_local = True
        self.chip_id_map = {'56150': 'Hurricane2'}
    def get_chipname(self):
        unit_str = self.bs.run('show unit')
        unit_re = re.compile('Unit \d+ chip BCM(?P<chip_id>\d+)')
        m = unit_re.match(unit_str)
        if not m:
            raise RuntimeError('could not determine the platform from unit string:\n\n%s\n' % unit_str)
        chip_id = m.group('chip_id')
        return self.get_id_2_name(chip_id)
    def collect_data(self):
        l3intf = re.compile("""
         \d+\s+                                   # Unit
         (\d+)\s+                                 # Intf
         \d+\s+                                   # VRF
         \d+\s+                                   # Group
         \d+\s+                                   # VLAN
         ([^ ]+)\s+                               # Source Mac
         \d+\s+                                   # MTU
        """, re.VERBOSE)
        for line in self.bs.run("l3 intf show").splitlines():
            parsed = l3intf.findall(line)
            if parsed:
                intf, mac = parsed[0]
                self.intfs[int(intf)] = mac
        host = re.compile("""
         \d+\s+                                   # Entry
         (\d+)\s+                                 # VRF
         ([^ ]+)\s+                               # prefix
         [^ ]+\s+                                 # Mac Address
         (\d+)                                    # INTF
        """, re.VERBOSE)
        for line in self.bs.run("l3 l3table show").splitlines():
            parsed = host.findall(line)
            if parsed:
                vrf, prefix, ndx = parsed[0]
                vrf = int(vrf)
                assert (vrf, prefix) not in self.hosts,                    "'l3 l3table show' (%s, %s) is already in %s" % (vrf, prefix, pformat(self.hosts))
                self.hosts[(vrf, prefix)] = ndx
        for line in self.bs.run("l3 ip6host show").splitlines():
            parsed = host.findall(line)
            if parsed:
                vrf, prefix, ndx = parsed[0]
                vrf = int(vrf)
                prefix = rfc5952(prefix)
                if self.split_ipv6_local:
                    assert (vrf, prefix) not in self.hostsipv6,                        "'l3 ip6host show' (%s, %s) is already in %s" % (vrf, prefix, pformat(self.hostsipv6))
                    self.hostsipv6[(vrf, prefix)] = ndx
                else:
                    assert (vrf, prefix) not in self.hosts,                        "'l3 ip6host show' (%s, %s) is already in %s" % (vrf, prefix, pformat(self.hosts))
                    self.hosts[(vrf, prefix)] = ndx
        egress = re.compile("""
         (\d+)\s+                                 # Entry
         ([^ ]+)                                  # Mac addr
         \s+\d+\s+                                # Vlan
         (\d+)                                    # INTF
        """, re.VERBOSE)
        for line in self.bs.run("l3 egress show").splitlines():
            parsed = egress.findall(line)
            if parsed:
                egress_ndx, mac, intf = parsed[0]
                assert egress_ndx not in self.egresses,                    "'l3 egress show' %s is already in %s" % (egress_ndx, pformat(self.egresses))
                self.egresses[egress_ndx] = (int(intf), mac)
        multipath = re.compile("Multipath Egress Object (\d+)")
        ref_count = re.compile("Reference count: (\d+)")
        interfaces = re.compile("(\d+)")
        mp_ndx = None
        for line in self.bs.run("l3 multipath show").splitlines():
            mparsed = multipath.findall(line)
            iparsed = interfaces.findall(line)
            rparsed = ref_count.findall(line)
            if mparsed:
                mp_ndx = mparsed[0]
            elif rparsed:
                pass
            elif iparsed:
                self.multipaths.setdefault(mp_ndx, []).extend(iparsed)
        route = re.compile("""
         \d+\s+                                    # '#'
         (\d+)\s+                                  # VRF
         ([^ ]+)\s+                                # Net addr prefix/len
         [^ ]+\s+                                  # Next Hop Mac addr
         (\d+)                                     # INTF
        """, re.VERBOSE)
        for line in self.bs.run("l3 defip show").splitlines():
            parsed = route.findall(line)
            if parsed:
                vrf, dest, ndx = parsed[0]
                vrf = int(vrf)
                if vrf:
                    vrf += 1000
                assert (vrf, dest) not in self.ip4_routes,                    "'l3 defip show' (%s, %s) is already in %s" % (vrf, dest, pformat(self.ip4_routes))
                self.ip4_routes[(vrf, dest)] = ndx
                if ndx in self.egresses:
                    self.route_2_rif[(vrf, dest)] = ndx
                elif ndx in self.multipaths:
                    self.route_2_ecmp[(vrf, dest)] = ndx
        for line in self.bs.run("l3 ip6route show").splitlines():
            parsed = route.findall(line)
            if parsed:
                vrf, dest, ndx = parsed[0]
                vrf = int(vrf)
                if vrf:
                    vrf += 1000
                dest = rfc5952(dest)
                assert (vrf, dest) not in self.ip6_routes,                    "'l3 ip6route show' (%s, %s) is already in %s" % (vrf, dest, pformat(self.ip6_routes))
                self.ip6_routes[(vrf, dest)] = ndx
                if ndx in self.egresses:
                    self.route_2_rif[(vrf, dest)] = ndx
                elif ndx in self.multipaths:
                    self.route_2_ecmp[(vrf, dest)] = ndx
        self.bs.close()
        self.routes = dict(self.ip4_routes.items() + self.ip6_routes.items())
        self.build_route_dict()
class Mellanox(Asic):
    def __init__(self):
        Asic.__init__(self)
        self.mp_exceptions = {0: None}
        self.neighbor_table = {}
    def get_chipname(self):
        pass
    def _read_ip_route_entries(self, source_table, ip_version):
        for p, entry in source_table.items():
            prefix = p
            ndx = 0
            target_table = self.ip4_routes
            if ip_version == 'ipv6':
                prefix = rfc5952(prefix)
                target_table = self.ip6_routes
            vrid = int(entry['vrid'])
            if vrid:
                vrid += 1000
            entry_type = entry['type']
            action = entry['action']
            rif = int(entry['erif'])
            if entry_type == 'NEXT_HOP' and action != 'TRAP':
                ecmp_id = int(entry['ecmp_id'])
                eid = ecmp_id + 100000
                if eid not in self.multipaths:
                    self.multipaths[eid] = []
                    ecmp_table = mlx_get_ecmp(ecmp_id=ecmp_id)
                    if ecmp_table:
                        i = 0
                        for erif in ecmp_table[ecmp_id]:
                            key = '{0} {1}'.format(entry['next_hops'][i], erif)
                            i += 1
                            if key in self.neighbor_table:
                                self.multipaths[eid].append(self.neighbor_table.keys().index(key))
                if entry['next_hop_cnt'] == 1:
                    if self.multipaths[eid]:
                        ndx = self.multipaths[eid][0]
                        self.route_2_rif[(vrid, prefix)] = ndx
                else:
                    ndx = ecmp_id + 100000
                self.route_2_ecmp[(vrid, prefix)] = ecmp_id + 100000
            elif entry_type == 'LOCAL':
                ndx = rif + 200000
                self.route_2_rif[(vrid, prefix)] = ndx
                self.egresses[ndx] = (rif, "00:00:00:00:00:00")
            if action == 'TRAP' or action == 'DROP':
                self.hroutes_exceptions.update(set([(vrid, prefix)]))
            target_table[(vrid, prefix)] = ndx
    def collect_data(self):
        mlx_open_connection()
        self.neighbor_table = mlx_get_neighbor()
        neighbor_table6 = mlx_get_neighbor(version=6)
        self.neighbor_table.update(neighbor_table6)
        for p, neighbor in self.neighbor_table.iteritems():
            vrid = int(neighbor['vrid'])
            if vrid:
                vrid += 1000
            ndx = self.neighbor_table.keys().index(p)
            rif = int(neighbor['rif'])
            prefix = p.split(' ')[0]
            if ':' in prefix:
                prefix = rfc5952(prefix)
            self.hosts[(vrid, prefix)] = ndx
            self.egresses[ndx] = (rif, neighbor['mac'])
        route_table = mlx_get_uc_route()
        self._read_ip_route_entries(route_table, 'ipv4')
        route_table = mlx_get_uc_route(6)
        self._read_ip_route_entries(route_table, 'ipv6')
        self.build_route_dict()
        mlx_close_connection()
class DiffTestParam(object):
    def __init__(self, set1, set2, exceptions, msg, key):
        self.set1 = set1
        self.set2 = set2
        self.exceptions = exceptions
        self.msg = msg
        self.key = key
        self.results = None
class Comparer(object):
    def __init__(self, kernel, asic):
        self.kernel = kernel
        self.asic = asic
        self.ret_code = 0
        self.diff_dict = {}
    def _s_diff_t(self, s, t, exceptions):
        return sorted(s.difference(t).difference(exceptions))
    def s_diff_t(self, diff_test_param):
        return self._s_diff_t(diff_test_param.set1,
                              diff_test_param.set2,
                              diff_test_param.exceptions)
    def run_tests(self, check_tuples, return_bit=0):
        for i, diff_test_param in enumerate(check_tuples, return_bit):
            diff = self.s_diff_t(diff_test_param)
            if diff:
                diff_test_param.results = diff
                if diff_test_param.key in self.diff_dict:
                    self.diff_dict[diff_test_param.key].append(diff_test_param)
                else:
                    self.diff_dict[diff_test_param.key] = [diff_test_param]
                self.ret_code = 1
    def get_kernel_route_exceptions(self):
        kernel_route_exceptions = []
        for ip in self.kernel_neigh_prefixes:
            if '.' in ip:
                ip = ip + '/32'
            else:
                ip = ip + '/128'
            if (0, ip) in self.kernel_prefixes:
                kernel_route_exceptions.append((0, ip))
        self.kernel.route_exceptions.update(set(kernel_route_exceptions))
        return self.kernel.route_exceptions
    def compute_deltas(self):
        self.asic_hosts_ndxs     = set(self.asic.hosts.itervalues())
        self.asic_egress_ndxs    = set(self.asic.egresses.iterkeys())
        self.asic_routes_rifs    = set(self.asic.route_2_rif.itervalues())
        self.asic_routes_ecmps   = set(self.asic.route_2_ecmp.itervalues())
        self.asic_mp_ndxs        = set(self.asic.multipaths.iterkeys())
        self.asic_mp_egress_ndxs = set([n for v in self.asic.multipaths.itervalues() for n in v])
        self.asic_all_ndxs       = self.asic_egress_ndxs.union(self.asic_mp_ndxs)
        self.kernel_neigh_prefixes = set()
        for (ip, iface) in self.kernel.neighs.iterkeys():
            self.kernel_neigh_prefixes.add(ip)
        self.asic_hosts_prefixes = []
        for (vrf, ip) in self.asic.hosts.iterkeys():
            self.asic_hosts_prefixes.append(ip)
        self.asic_hosts_prefixes = set(self.asic_hosts_prefixes)
        self.asic_hostsipv6_prefixes = set(self.asic.hostsipv6.iterkeys())
        self.asic_routes_prefixes = set(self.asic.ip4_routes.iterkeys())
        self.asic_routes_prefixes.update(set(self.asic.ip6_routes.iterkeys()))
        self.asic_hroutes_prefixes = set(self.asic.hroutes.iterkeys())
        self.kernel_prefixes = set(self.kernel.routes.iterkeys())
        self.kernel_ipv6local_prefixes = set(self.kernel.ipv6_local_routes.iterkeys())
        self.kernel_mac = set(self.kernel.neigh_macs.iterkeys())
        self.asic_egress_mac = set([mac for intf, mac in self.asic.egresses.itervalues()])
        adjacency_exceptions = set(["00:00:00:00:00:00"])  
        ip4route_exceptions = set([(0, "0.0.0.0/0")])    
        ip6route_exceptions = set([(0, ":/0")])          
        hroutes_exceptions = ip4route_exceptions
        hroutes_exceptions.update(ip6route_exceptions)
        hroutes_exceptions.update(self.asic.hroutes_exceptions)
        kernel_route_exceptions = self.get_kernel_route_exceptions()
        kernel_neigh_exceptions = self.kernel.neigh_exceptions
        if isinstance(self.asic, Broadcom):
            for (prefix, paths) in self.kernel.onlinks.iteritems():
                (prefix, prefixlen) = prefix.split('/')
                if prefixlen == '32' and '.' in prefix:
                    for (iface, _) in paths:
                        if (prefix, iface) in self.kernel.neighs:
                            kernel_neigh_exceptions.update(set([prefix]))
        for (vrf_name, vrf_id) in self.kernel.vrfs.iteritems():
            hroutes_exceptions.update(set([(vrf_id, "0.0.0.0/0"), (vrf_id, ":/0")]))
        check_tuples = (
            DiffTestParam(self.asic_mp_egress_ndxs,
                          self.asic_egress_ndxs,
                          set(),
                          "asic multipath table has index(es) not found in asic egress table",
                          "mp_not_in_egress"),
            DiffTestParam(self.asic_mp_ndxs,
                          self.asic_routes_ecmps,
                          set(),
                          "asic multipath table has index(es) not found in asic route tables",
                          "mptable_idx_not_in_routes"),
            DiffTestParam(self.asic_routes_rifs,
                          self.asic_egress_ndxs,
                          set(),
                          "asic route tables have interface index(es) not found in asic egress table",
                          "ip4_ip6_route_idx_not_in_egress"),
            DiffTestParam(self.asic_routes_ecmps,
                          self.asic_mp_ndxs,
                          self.asic.mp_exceptions,
                          "asic route tables have ecmp index(es) not found in asic multipath table",
                          "route_idx_not_in_mptables"),
            DiffTestParam(self.asic_hosts_ndxs,
                          self.asic_egress_ndxs,
                          set(),
                          "asic host tables have index(es) not found in asic egress table",
                          "host_tables_idx_not_in_egress"),
            DiffTestParam(self.asic_routes_prefixes,
                          self.asic_hroutes_prefixes,
                          hroutes_exceptions,
                          "asic route tables have prefix(es) not found in asic hroutes",
                          "route_prefix_not_in_hroutes"),
            DiffTestParam(self.asic_routes_prefixes,
                          self.kernel_prefixes,
                          hroutes_exceptions,
                          "route tables have prefix(es) not found in kernel routes",
                          "route_prefix_not_in_kernel_routes"),
            DiffTestParam(self.kernel_prefixes,
                          self.asic_routes_prefixes,
                          kernel_route_exceptions,
                          "kernel routes has prefix(es) not found in asic route tables",
                          "kernel_prefix_not_found_in_routes"),
            DiffTestParam(self.asic_egress_mac,
                          self.kernel_mac,
                          adjacency_exceptions,
                          "asic egress table has mac not found in kernel neighbors",
                          "egress_table_mac_not_in_kernel_neighbors"),
            DiffTestParam(self.asic_hosts_prefixes,
                          self.kernel_neigh_prefixes,
                          set(),
                          "asic host table has prefix(es) not found in kernel neigh table",
                          "host_table_prefix_not_in_kernel_neigh_table"),
            DiffTestParam(self.kernel_neigh_prefixes,
                          self.asic_hosts_prefixes,
                          kernel_neigh_exceptions,
                          "kernel neigh table has prefix(es) not found in asic host table",
                          "kernel_neigh_table_prefix_not_in_host_table")
        )
        self.run_tests(check_tuples)
        nhs_lens = [len(nhs) for nhs in self.asic.hroutes.itervalues()]
        ave_len = 0
        if len(nhs_lens) > 0:
            ave_len = sum(nhs_lens) / float(len(nhs_lens))
        doing_ecmp = ave_len > 1.0
        for dest, hnhs in self.asic.hroutes.items():
            if dest in self.kernel.routes and dest not in self.kernel.route_exceptions:
                knhs = self.kernel.routes[dest]
                km = []
                for iface, mac in knhs:
                    if not mac:
                        if not self.kernel.ifaces.get(iface):
                            continue
                        else:
                            mac = self.kernel.ifaces[iface][1]
                    elif mac == 'no neighbor found':
                        continue
                    km.append(mac)
                knhs = km
                hm = []
                for intf, mac in hnhs:
                    if mac != "00:00:00:00:00:00":
                        hm.append(mac)
                hnhs = hm
                if doing_ecmp and len(hnhs) == 1 and len(knhs) > 1 and hnhs[0] in knhs:
                    knhs = hnhs
                check_tuples = (
                    DiffTestParam(set(hnhs),
                                  set(knhs),
                                  set(),
                                  "route '%s' has next hop in HW not found in kernel" % pformat(dest),
                                  "route_nexthop_in_hw_not_in_kernel"),
                    DiffTestParam(set(knhs),
                                  set(hnhs),
                                  set(),
                                  "route '%s' has next hop in kernel not found in HW" % pformat(dest),
                                  "route_nexthop_in_kernel_not_in_hw")
                )
                self.run_tests(check_tuples, 11)
        if self.asic.split_ipv6_local:
            check_tuples = (
                DiffTestParam(self.asic_hostsipv6_prefixes,
                              self.kernel_ipv6local_prefixes,
                              set(),
                              "IPv6 host table has prefix(es) not found in kernel IPv6 local route table",
                              "ipv6_host_table_prefix_not_in_kernel_ipv6_local_route_table"),
                DiffTestParam(self.kernel_ipv6local_prefixes,
                              self.asic_hostsipv6_prefixes,
                              set(),
                              "kernel IPv6 local route table has prefix(es) not found in IPv6 host table",
                              "kernel_ipv6_local_route_table_prefix_not_in_ipv6_host_table")
            )
            self.run_tests(check_tuples)
    def get_result_json(self):
        if not self.diff_dict:
            return json.dumps({'status': 'success'})
        out_sio = StringIO()
        data_dict = {}
        for diff_key in self.diff_dict:
            if len(self.diff_dict[diff_key]) > 1:
                res_list = []
                for dtparam_obj in self.diff_dict[diff_key]:
                    res_list.extend(dtparam_obj.results)
                data_dict[diff_key] = res_list
            else:
                data_dict[diff_key] = self.diff_dict[diff_key][0].results
        out_dict = {'status': 'error',
                    'data': data_dict}
        json.dump(out_dict, out_sio)
        return out_sio.getvalue()
    def get_result_str(self, verbose, very_verbose):
        if not self.diff_dict:
            return ''
        out_sio = StringIO()
        print_missing_neighbors = False
        for diff_obj_list in sorted(self.diff_dict.values()):
            if diff_obj_list:
                if not print_missing_neighbors:
                    print_missing_neighbors = True
                    if self.kernel.missing_neighbors:
                        print >> out_sio, "There are routes via the following nexthops but the kernel does not have neighbors for these:"
                        for (table, iface, via) in self.kernel.missing_neighbors:
                            print >> out_sio, "  table %s has a route via %s with nexthop %s" % (table, iface, via)
                        print >> out_sio, ""
                for diff_obj in diff_obj_list:
                    print >> out_sio, "%s:" % diff_obj.msg
                    for i, diff in enumerate(diff_obj.results):
                        if i > 2 and not (verbose or very_verbose):
                            print >> out_sio, "   more..."
                            break
                        print >> out_sio, "  ", diff
        return out_sio.getvalue()
    def print_all(self):
        print "asic_hosts_ndxs\n%s\n" % pformat(self.asic_hosts_ndxs)
        print "asic_routes_rifs\n%s\n" % pformat(self.asic_routes_rifs)
        print "asic_routes_ecmp\n%s\n" % pformat(self.asic_routes_ecmps)
        print "asic_egress_ndxs\n%s\n" % pformat(self.asic_egress_ndxs)
        print "asic_mp_ndxs\n%s\n" % pformat(self.asic_mp_ndxs)
        print "asic_mp_egress_ndxs\n%s\n" % pformat(self.asic_mp_egress_ndxs)
        print "asic_all_ndxs\n%s\n" % pformat(self.asic_all_ndxs)
        print "kernel_vrfs\n%s\n" % pformat(self.kernel.vrfs)
        print "kernel_neigh_prefixes\n%s\n" % pformat(self.kernel_neigh_prefixes)
        print "asic_hosts_prefixes\n%s\n" % pformat(self.asic_hosts_prefixes)
        print "asic_routes_prefixes\n%s\n" % pformat(self.asic_routes_prefixes)
        print "kernel_prefixes\n%s\n" % pformat(self.kernel_prefixes)
        print "asic_hroutes_prefixes\n%s\n" % pformat(self.asic_hroutes_prefixes)
        print "kernel_mac\n%s\n" % pformat(self.kernel_mac)
        print "asic_egress_mac\n%s\n" % pformat(self.asic_egress_mac)
    def print_very_verbose(self):
        print "interfaces"
        print "----------"
        for (key, value) in sorted(self.kernel.ifaces.iteritems()):
            print "%r: %r" % (key, value)
        print
        print "kernel neighbors"
        print "----------------"
        for (key, value) in sorted(self.kernel.neighs.iteritems()):
            print "%r: %r" % (key, value)
        print
        print "kernel neighbors exceptions"
        print "---------------------------"
        pprint(self.kernel.neigh_exceptions)
        print
        print "kernel neighbor macs"
        print "--------------------"
        for (key, value) in sorted(self.kernel.neigh_macs.iteritems()):
            print "%r: %r" % (key, value)
        print
        print "kernel vrfs"
        print "--------------------"
        for (key, value) in sorted(self.kernel.vrfs.iteritems()):
            print "%r: %r" % (key, value)
        print
        print "kernel routes"
        print "-------------"
        for dest in sorted(self.kernel.routes.iterkeys()):
            print "%r: %r" % (dest, self.kernel.routes[dest])
        print
        print "kernel onlink routes"
        print "--------------------"
        pprint(self.kernel.onlinks)
        print
        if self.asic.split_ipv6_local:
            print "kernel IPv6 local routes"
            print "------------------------"
            for dest in sorted(self.kernel.ipv6_local_routes.iterkeys()):
                print "%r: %r" % (dest, self.kernel.ipv6_local_routes[dest])
            print
        print "HW intfs"
        print "--------"
        for intf in sorted(self.asic.intfs.iterkeys()):
            print "%r: %r" % (intf, self.asic.intfs[intf])
        print
        print "HW hosts"
        print "--------"
        for prefix in sorted(self.asic.hosts.iterkeys()):
            print "%r: %r" % (prefix, self.asic.hosts[prefix])
        print
        if self.asic.split_ipv6_local:
            print "HW IPv6 hosts"
            print "--------------------"
            for prefix in sorted(self.asic.hostsipv6.iterkeys()):
                print "%r: %r" % (prefix, self.asic.hostsipv6[prefix])
            print
        print "HW egress"
        print "---------"
        for egress_ndx in sorted(self.asic.egresses.iterkeys()):
            print "%r: %r" % (egress_ndx, self.asic.egresses[egress_ndx])
        print
        print "HW multipaths"
        print "-------------"
        for mp_ndx in sorted(self.asic.multipaths.iterkeys()):
            print "%r: %r" % (mp_ndx, self.asic.multipaths[mp_ndx])
        print
        print "HW IPv4 routes"
        print "--------------"
        for dest in sorted(self.asic.ip4_routes.iterkeys()):
            print "%r: %r" % (dest, self.asic.ip4_routes[dest])
        print
        print "HW IPv6 routes"
        print "--------------"
        for dest in sorted(self.asic.ip6_routes.iterkeys()):
            print "%r: %r" % (dest, self.asic.ip6_routes[dest])
        print
        print "HW routes"
        print "---------"
        for dest in sorted(self.asic.hroutes.iterkeys()):
            print "%r: %r" % (dest, self.asic.hroutes[dest])
        print
def get_non_swp_config():
    ignore_non_swps = False
    swp_config_file = '/cumulus/switchd/config/ignore_non_swps'
    try:
        with open(swp_config_file, 'r') as f:
            config_str = f.readline()
    except IOError:
        print 'ignore_non_swp config not available'
        return True
    config_str = config_str.rstrip()
    if config_str.lower() == 'true':
        ignore_non_swps = True
    return ignore_non_swps
if __name__ == '__main__':
    if os.getuid():
        print "Sorry, need to run with admin privs"
        sys.exit(1)
    parser = argparse.ArgumentParser(
        description="cl-route-check: verify kernel neighbor/route tables vs. hardware neighbor/route tables",
    )
    mode = parser.add_mutually_exclusive_group(required=False)
    mode.add_argument('-j', '--json', default=False, action='store_true', help='JSON output')
    mode.add_argument('-r', '--raw', default=False, action='store_true', help='Raw data for debugging')
    mode.add_argument('-v', '--verbose', default=False, action='store_true')
    mode.add_argument('-V', '--very-verbose', default=False, action='store_true')
    parser.add_argument('--version', action='version', version='%(prog)s 1.1')
    args = parser.parse_args()
    ignore_non_swps = get_non_swp_config()
    kernel = Kernel()
    platform_object = cumulus.platforms.probe()
    chip = platform_object.switch.chip
    if chip.sw_base == 'bcm':
        asic = Broadcom()
    elif chip.sw_base == 'mlx':
        asic = Mellanox()
        from cumulus.mlx import mlx_open_connection
        from cumulus.mlx import mlx_close_connection
        from cumulus.mlx import mlx_get_intf
        from cumulus.mlx import mlx_get_interface
        from cumulus.mlx import mlx_get_neighbor
        from cumulus.mlx import mlx_get_uc_route
        from cumulus.mlx import mlx_get_ecmp
    else:
        raise NotImplementedError
    kernel.collect_data()
    asic.collect_data()
    comp = Comparer(kernel, asic)
    comp.compute_deltas()
    if args.json:
        sys.stdout.write(comp.get_result_json() + '\n')
    else:
        sys.stdout.write(comp.get_result_str(args.verbose, args.very_verbose) + '\n')
        if args.raw:
            comp.print_all()
        elif args.very_verbose:
            comp.print_very_verbose()
    sys.exit(comp.ret_code)

