#!/usr/bin/env python

# Copyright 2018, LabN Consulting, L.L.C.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; see the file COPYING; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

#
# want_rd_routes = [
#     {'rd':'10:1', 'p':'5.1.0.0/24', 'n':'1.1.1.1', 'bp': True},
#     {'rd':'10:1', 'p':'5.1.0.0/24', 'n':'1.1.1.1', 'bp': False},
#
#     {'rd':'10:3', 'p':'5.1.0.0/24', 'n':'3.3.3.3'},
# ]
#
# ribRequireVpnRoutes('r2','Customer routes',want_rd_routes)
#
# want_unicast_routes = [
#     {'p':'5.1.0.0/24', 'n':'1.1.1.1'},
# ]
#
# ribRequireUnicastRoutes('r1','ipv4','r1-cust1','Customer routes in vrf',want_unicast_routes)
# ribRequireUnicastRoutes('r1','ipv4','','Customer routes in default',want_unicast_routes)
#

from lib.lutil import luCommand, luResult, LUtil
import json
import re

# gpz: get rib in json form and compare against desired routes
class BgpRib:
    def log(self, str):
        LUtil.log("BgpRib: " + str)

    def routes_include_wanted(self, pfxtbl, want, debug):
        # helper function to RequireVpnRoutes
        for pfx in pfxtbl.keys():
            if debug:
                self.log("trying pfx %s" % pfx)
            if pfx != want["p"]:
                if debug:
                    self.log("want pfx=" + want["p"] + ", not " + pfx)
                continue
            if debug:
                self.log("have pfx=%s" % pfx)
            for r in pfxtbl[pfx]:
                bp = r.get("bestpath", False)
                if debug:
                    self.log("trying route %s bp=%s" % (r, bp))
                nexthops = r["nexthops"]
                for nh in nexthops:
                    if debug:
                        self.log("trying nh %s" % nh["ip"])
                    if nh["ip"] == want["n"]:
                        if debug:
                            self.log("found %s" % want["n"])
                        if bp == want.get("bp", bp):
                            return 1
                        elif debug:
                            self.log("bestpath mismatch %s != %s" % (bp, want["bp"]))
                    else:
                        if debug:
                            self.log("want nh=" + want["n"] + ", not " + nh["ip"])
            if debug:
                self.log("missing route: pfx=" + want["p"] + ", nh=" + want["n"])
            return 0

    def RequireVpnRoutes(self, target, title, wantroutes, debug=0):
        import json

        logstr = "RequireVpnRoutes " + str(wantroutes)
        # non json form for humans
        luCommand(
            target,
            'vtysh -c "show bgp ipv4 vpn"',
            ".",
            "None",
            "Get VPN RIB (non-json)",
        )
        ret = luCommand(
            target,
            'vtysh -c "show bgp ipv4 vpn json"',
            ".*",
            "None",
            "Get VPN RIB (json)",
        )
        if re.search(r"^\s*$", ret):
            # degenerate case: empty json means no routes
            if len(wantroutes) > 0:
                luResult(target, False, title, logstr)
                return
            luResult(target, True, title, logstr)
        rib = json.loads(ret)
        rds = rib["routes"]["routeDistinguishers"]
        for want in wantroutes:
            found = 0
            if debug:
                self.log("want rd %s" % want["rd"])
            for rd in rds.keys():
                if rd != want["rd"]:
                    continue
                if debug:
                    self.log("found rd %s" % rd)
                table = rds[rd]
                if self.routes_include_wanted(table, want, debug):
                    found = 1
                    break
            if not found:
                luResult(target, False, title, logstr)
                return
        luResult(target, True, title, logstr)

    def RequireUnicastRoutes(self, target, afi, vrf, title, wantroutes, debug=0):
        logstr = "RequireUnicastRoutes %s" % str(wantroutes)
        vrfstr = ""
        if vrf != "":
            vrfstr = "vrf %s" % (vrf)

        if (afi != "ipv4") and (afi != "ipv6"):
            self.log("ERROR invalid afi")

        cmdstr = "show bgp %s %s unicast" % (vrfstr, afi)
        # non json form for humans
        cmd = 'vtysh -c "%s"' % cmdstr
        luCommand(target, cmd, ".", "None", "Get %s %s RIB (non-json)" % (vrfstr, afi))
        cmd = 'vtysh -c "%s json"' % cmdstr
        ret = luCommand(
            target, cmd, ".*", "None", "Get %s %s RIB (json)" % (vrfstr, afi)
        )
        if re.search(r"^\s*$", ret):
            # degenerate case: empty json means no routes
            if len(wantroutes) > 0:
                luResult(target, False, title, logstr)
                return
            luResult(target, True, title, logstr)
        rib = json.loads(ret)
        try:
            table = rib["routes"]
            # KeyError: 'routes' probably means missing/bad VRF
        except KeyError as err:
            if vrf != "":
                errstr = "-script ERROR: check if wrong vrf (%s)" % (vrf)
            else:
                errstr = "-script ERROR: check if vrf missing"
            luResult(target, False, title + errstr, logstr)
            return
        # if debug:
        #    self.log("table=%s" % table)
        for want in wantroutes:
            if debug:
                self.log("want=%s" % want)
            if not self.routes_include_wanted(table, want, debug):
                luResult(target, False, title, logstr)
                return
        luResult(target, True, title, logstr)


BgpRib = BgpRib()


def bgpribRequireVpnRoutes(target, title, wantroutes, debug=0):
    BgpRib.RequireVpnRoutes(target, title, wantroutes, debug)


def bgpribRequireUnicastRoutes(target, afi, vrf, title, wantroutes, debug=0):
    BgpRib.RequireUnicastRoutes(target, afi, vrf, title, wantroutes, debug)
