#!/usr/bin/env python3
#
#
#             10.0.0.1/32 (lo)
#                |
#         +------+---------+ .1   10.0.12.0/30         .2+--------------------+
#         |      R1        +-----------------------------+      R2            |
#         |                |       AREA0                 |                    +-10.0.0.2/32 (lo)
#         +-------+--------+--.1                     .1 -+--------+-----------+
#              .2 |           \---                  ---/          |.1
#                 |               \---         ----/              |
#   10.0.41.0 /30 | AREA2             \--- ---/                   | 10.0.23.0/30
#                 |       10.0.24.0/30 ---/\---  10.0.13.0/30     |
#                 |               ----/        \---       AREA1   |
#              .1 |        .2 ---/                  \--- .2       |.2
#         +-------+--------+-/                        \-+--------+----------+
#         |   R4        .2 |                             |   R3              |
#         |                +---------+                 .1|                   +-10.0.0.3/32 (lo)
#         +------+---------+.1       | 192.168.4.0/24    +-------------------+
#                |                   |.2
#          10.0.0.4/32 (lo)  +-------+
#                            |       |
#                            |  PC   |
#                            |       |
#                            +-------+
#
#
"""OSPF with multiple areas

This test evaluates various OSPF features across three areas (one NSSA area, with no summary)
to ensure that route distribution is deterministic (based on cost). It also tests link
failures using BFD, though BFD is not yet implemented in test framework (Infamy).

This test also verifies broadcast and point-to-point interface types on /30 network, and
explicit router-id.

....
  +-------------+  +---------------+  +-------------+  +---------------+
  |     R1      |  |      R2       |  |     R3      |  |      R4       |
  | 10.0.0.1/32 |  |  10.0.0.2/32  |  | 10.0.0.3/32 |  |  10.0.0.4/32  |
  |   (lo)      |  |  11.0.9.1/24  |  |   (lo)      |  |      (lo)     |
  +-------------+  |  11.0.10.1/24 |  +-------------+   +---------------+
                   |  11.0.11.1/24 |
                   |  11.0.12.1/24 |
                   |  11.0.13.1/24 |
                   |  11.0.14.1/24 |
                   |  11.0.15.1/24 |
                   |      (lo)     |
                   +---------------+
....
"""
import infamy

import infamy.route as route
from infamy.util import until, parallel
def config_target1(target, ring1, ring2, cross):
    target.put_config_dicts({
        "ietf-interfaces": {
            "interfaces": {
                "interface": [
                    {
                        "name": ring1,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "10.0.12.1",
                                "prefix-length": 30
                            }]
                        }
                    },
                    {
                        "name": ring2,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "10.0.41.2",
                                "prefix-length": 30
                            }]
                        }
                    },
                    {
                        "name": cross,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "10.0.13.1",
                                "prefix-length": 30
                            }]
                        }
                    },
                    {
                        "name": "lo",
                        "enabled": True,
                        "ipv4": {
                            "address": [
                                {
                                    "ip": "11.0.8.1",
                                    "prefix-length": 24
                                },
                                {
                                    "ip": "10.0.0.1",
                                    "prefix-length": 32
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "ietf-routing": {
            "routing": {
                "control-plane-protocols": {
                    "control-plane-protocol": [
                        {
                            "type": "infix-routing:ospfv2",
                            "name": "default",
                            "ospf": {
                                "explicit-router-id": "10.0.0.1",
                                "areas": {
                                    "area": [
                                        {
                                            "area-id": "0.0.0.0",
                                            "interfaces": {
                                                "interface": [
                                                    {
                                                        "bfd": {
                                                            "enabled": True
                                                        },
                                                        "name": ring1,
                                                        "hello-interval": 1,
                                                        "enabled": True
                                                    }
                                                ]
                                            }
                                        },
                                        {
                                            "area-id": "0.0.0.1",
                                            "area-type": "nssa-area",
                                            "summary": False,
                                            "interfaces": {
                                                "interface": [
                                                    {
                                                        "bfd": {
                                                            "enabled": True
                                                        },
                                                        "name": cross,
                                                        "hello-interval": 1,
                                                        "enabled": True,
                                                        "cost": 2000
                                                    },
                                                    {
                                                        "name": "lo",
                                                        "enabled": True
                                                    }
                                                ]
                                            }
                                        },
                                        {
                                            "area-id": "0.0.0.2",
                                            "interfaces": {
                                                "interface": [
                                                    {
                                                        "bfd": {
                                                            "enabled": True
                                                        },
                                                        "name": ring2,
                                                        "hello-interval": 1,
                                                        "enabled": True,
                                                        "interface-type": "point-to-point"
                                                    }
                                                ]
                                            }
                                        }
                                    ]
                                }
                            }
                        }
                    ]
                }
            }
        },
        "ietf-system": {
            "system": {
                "hostname": "R1"
            }
        }
    })

def config_target2(target, ring1, ring2, cross):
    target.put_config_dicts({
        "ietf-interfaces": {
            "interfaces": {
                "interface": [
                    {
                        "name": ring1,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "10.0.23.1",
                                "prefix-length": 30
                            }]
                        }
                    },
                    {
                        "name": ring2,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "10.0.12.2",
                                "prefix-length": 30
                            }]
                        }
                    },
                    {
                        "name": cross,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "10.0.24.1",
                                "prefix-length": 30
                            }]
                        }
                    },
                    {
                        "name": "lo",
                        "enabled": True,
                        "ipv4": {
                            "address": [
                                {
                                    "ip": "10.0.0.2",
                                    "prefix-length": 32
                                },
                                {
                                    "ip": "11.0.9.1",
                                    "prefix-length": 24
                                },
                                {
                                    "ip": "11.0.10.1",
                                    "prefix-length": 24
                                },
                                {
                                    "ip": "11.0.11.1",
                                    "prefix-length": 24
                                },
                                {
                                    "ip": "11.0.12.1",
                                    "prefix-length": 24
                                },
                                {
                                    "ip": "11.0.13.1",
                                    "prefix-length": 24
                                },
                                {
                                    "ip": "11.0.14.1",
                                    "prefix-length": 24
                                },
                                {
                                    "ip": "11.0.15.1",
                                    "prefix-length": 24
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "ietf-system": {
            "system": {
                "hostname": "R2"
            }
        },
        "ietf-routing": {
            "routing": {
                "control-plane-protocols": {
                    "control-plane-protocol": [
                        {
                            "type": "infix-routing:ospfv2",
                            "name": "default",
                            "ospf": {
                                "explicit-router-id": "1.1.1.1",
                                "areas": {
                                    "area": [
                                        {
                                            "area-id": "0.0.0.0",
                                            "interfaces": {
                                                "interface": [
                                                    {
                                                        "bfd": {
                                                            "enabled": True
                                                        },
                                                        "name": ring2,
                                                        "hello-interval": 1,
                                                        "enabled": True
                                                    },
                                                    {
                                                        "name": "lo",
                                                        "enabled": True
                                                    }
                                                ]
                                            }
                                        },
                                        {
                                            "area-id": "0.0.0.1",
                                            "area-type": "nssa-area",
                                            "summary": False,
                                            "interfaces": {
                                                "interface": [
                                                    {
                                                        "bfd": {
                                                            "enabled": True
                                                        },
                                                        "name": ring1,
                                                        "hello-interval": 1,
                                                        "enabled": True
                                                    }
                                                ]
                                            }
                                        },
                                        {
                                            "area-id": "0.0.0.2",
                                            "interfaces": {
                                                "interface": [
                                                    {
                                                        "bfd": {
                                                            "enabled": True
                                                        },
                                                        "name": cross,
                                                        "hello-interval": 1,
                                                        "cost": 2000,
                                                        "enabled": True
                                                    }
                                                ]
                                            }
                                        }
                                    ]
                                }
                            }
                        }
                    ]
                }
            }
        }
    })

def config_target3(target, ring2, cross, link):
    target.put_config_dicts({
        "ietf-interfaces": {
            "interfaces": {
                "interface": [
                    {
                        "name": ring2,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "10.0.23.2",
                                "prefix-length": 30
                                }]
                            }
                    },
                    {
                        "name": link,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "192.168.3.1",
                                "prefix-length": 24
                            }]
                        }
                    },
                    {
                        "name": cross,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "10.0.13.2",
                                "prefix-length": 30
                            }]
                        }
                    },
                    {
                        "name": "lo",
                        "enabled": True,
                        "ipv4": {
                            "address": [{
                                "ip": "10.0.0.3",
                                "prefix-length": 32
                            }]
                        }
                        }
                    ]
                }
        },
        "ietf-system": {
                "system": {
                    "hostname": "R3"
                }
        },
        "ietf-routing": {
            "routing": {
                "control-plane-protocols": {
                    "control-plane-protocol": [{
                        "type": "infix-routing:ospfv2",
                        "name": "default",
                        "ospf": {
                            "explicit-router-id": "10.0.0.3",
                            "areas": {
                                "area": [{
                                    "area-id": "0.0.0.1",
                                    "area-type": "nssa-area",
                                    "summary": False,
                                    "interfaces": {
                                        "interface": [{
                                            "bfd": {
                                                "enabled": True
                                            },
                                            "name": cross,
                                            "hello-interval": 1,
                                            "enabled": True,
                                            "cost": 2000
                                        }, {
                                            "bfd": {
                                                "enabled": True
                                            },
                                            "name": ring2,
                                            "hello-interval": 1,
                                            "enabled": True
                                        }, {
                                            "name": link,
                                            "enabled": True,
                                            "passive": True
                                        }, {
                                            "name": "lo",
                                            "enabled": True
                                        }]
                                    }
                                }]
                            }
                        }
                    }]
                }
            }
        }
    })


def config_target4(target, ring1, cross, link):
    target.put_config_dicts({
        "ietf-interfaces": {
            "interfaces": {
                "interface": [
                    {
                        "name": ring1,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "10.0.41.1",
                                "prefix-length": 30
                            }]}
                    },
                    {
                        "name": cross,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "10.0.24.2",
                                "prefix-length": 30
                            }]}
                    },
                    {
                        "name": link,
                        "enabled": True,
                        "ipv4": {
                            "forwarding": True,
                            "address": [{
                                "ip": "192.168.4.1",
                                "prefix-length": 24
                            }]
                        }
                    },
                    {
                        "name": "lo",
                        "enabled": True,
                        "ipv4": {
                            "address": [{
                                "ip": "10.0.0.4",
                                "prefix-length": 32
                            }]
                        }
                    }
                ]
            }
        },
        "ietf-system": {
            "system": {
                "hostname": "R4"
            }
        },
        "ietf-routing": {
            "routing": {
                "control-plane-protocols": {
                    "control-plane-protocol": [
                        {
                            "type": "infix-routing:ospfv2",
                            "name": "default",
                            "ospf": {
                                "explicit-router-id": "10.0.0.4",
                                "redistribute": {
                                    "redistribute": [{
                                            "protocol": "connected"
                                        }]
                                },
                                "areas": {
                                    "area": [{
                                        "area-id": "0.0.0.2",
                                        "interfaces": {
                                            "interface": [{
                                                "bfd": {
                                                    "enabled": True
                                                },
                                                "name": ring1,
                                                "hello-interval": 1,
                                                "enabled": True,
                                                "interface-type": "point-to-point"
                                            }, {
                                                "bfd": {
                                                    "enabled": True
                                                },
                                                "name": cross,
                                                "hello-interval": 1,
                                                "enabled": True,
                                                "cost": 5000
                                            }, {
                                                "name": "lo",
                                                "enabled": True
                                            }]
                                        }
                                    }]
                                }
                            }
                        }
                    ]
                }
            }
        }
    })


def disable_link(target, link):
    target.put_config_dict("ietf-interfaces", {
        "interfaces": {
            "interface": [
                {
                    "name": link,
                    "enabled": False
                }]
        }
    })


with infamy.Test() as test:
    with test.step("Set up topology and attach to target DUTs"):
        env = infamy.Env()
        R1 = env.attach("R1", "mgmt")
        R2 = env.attach("R2", "mgmt")
        R3 = env.attach("R3", "mgmt")
        R4 = env.attach("R4", "mgmt")

        _, R1ring1 = env.ltop.xlate("R1", "ring1")
        _, R1ring2 = env.ltop.xlate("R1", "ring2")
        _, R2ring1 = env.ltop.xlate("R2", "ring1")
        _, R2ring2 = env.ltop.xlate("R2", "ring2")
        _, R3ring2 = env.ltop.xlate("R3", "ring2")
        _, R4ring1 = env.ltop.xlate("R4", "ring1")

        _, R3data = env.ltop.xlate("R3", "data")
        _, R4data = env.ltop.xlate("R4", "data")

        _, R1cross = env.ltop.xlate("R1", "cross")
        _, R2cross = env.ltop.xlate("R2", "cross")
        _, R3cross = env.ltop.xlate("R3", "cross")
        _, R4cross = env.ltop.xlate("R4", "cross")

    with test.step("Configure targets"):
        parallel(config_target1(R1, R1ring1, R1ring2, R1cross),
                 config_target2(R2, R2ring1, R2ring2, R2cross),
                 config_target3(R3, R3ring2, R3cross, R3data),
                 config_target4(R4, R4ring1, R4cross, R4data))

    with test.step("Wait for all neighbors to peer"):
        print("Waiting for neighbors to peer")
        until(lambda: route.ospf_get_neighbor(R1, "0.0.0.0", R1ring1, "1.1.1.1"), attempts=200)
        until(lambda: route.ospf_get_neighbor(R1, "0.0.0.1", R1cross, "10.0.0.3"), attempts=200)
        until(lambda: route.ospf_get_neighbor(R2, "0.0.0.1", R2ring1, "10.0.0.3"), attempts=200)
        until(lambda: route.ospf_get_neighbor(R2, "0.0.0.0", R2ring2, "10.0.0.1"), attempts=200)
        until(lambda: route.ospf_get_neighbor(R2, "0.0.0.2", R2cross, "10.0.0.4"), attempts=200)

    with test.step("Wait for routes from OSPF on all routers"):
        print("Waiting for routes from OSPF")
        until(lambda: route.ipv4_route_exist(R1, "10.0.0.2/32", nexthop="10.0.12.2", proto="ietf-ospf:ospfv2"), attempts=200)
        until(lambda: route.ipv4_route_exist(R1, "10.0.0.3/32", nexthop="10.0.13.2", proto="ietf-ospf:ospfv2"), attempts=200)
        until(lambda: route.ipv4_route_exist(R1, "10.0.0.4/32", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200)
        until(lambda: route.ipv4_route_exist(R1, "192.168.4.0/24", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200)
        until(lambda: route.ipv4_route_exist(R1, "10.0.24.0/30", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200)
        until(lambda: route.ipv4_route_exist(R2, "10.0.0.1/32", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200)
        until(lambda: route.ipv4_route_exist(R2, "10.0.0.3/32", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200)
        until(lambda: route.ipv4_route_exist(R2, "10.0.0.4/32", nexthop="10.0.24.2", proto="ietf-ospf:ospfv2"), attempts=200)
        until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0", nexthop="10.0.23.1", proto="ietf-ospf:ospfv2"), attempts=200)
        until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2", proto="ietf-ospf:ospfv2"), attempts=200)
        until(lambda: route.ipv4_route_exist(R2, "10.0.13.0/30", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200)

    with test.step("Verify Area 0.0.0.1 on R3 is NSSA area"):
        assert(route.ospf_is_area_nssa(R3, "0.0.0.1"))

    with test.step("Verify R1:ring2 is of type point-to-point"):
        assert(route.ospf_get_interface_type(R1, "0.0.0.2", R1ring2) == "point-to-point")

    with test.step("Verify R4:ring1 is of type point-to-point"):
        assert(route.ospf_get_interface_type(R4, "0.0.0.2", R4ring1) == "point-to-point")

    with test.step("Verify on R3, there are no routes beyond 10.0.23.1, just a default route"):
        # Should be only default route out of the area.
        parallel(lambda: until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0"), attempts=200),
                 lambda: until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") is False, attempts=5),
                 lambda: until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") is False, attempts=5),
                 lambda: until(lambda: route.ipv4_route_exist(R3, "11.0.8.0/24") is False, attempts=5),
                 lambda: until(lambda: route.ipv4_route_exist(R3, "11.0.9.0/24") is False, attempts=5),
                 lambda: until(lambda: route.ipv4_route_exist(R3, "11.0.10.0/24") is False, attempts=5),
                 lambda: until(lambda: route.ipv4_route_exist(R3, "11.0.11.0/24") is False, attempts=5),
                 lambda: until(lambda: route.ipv4_route_exist(R3, "11.0.12.0/24") is False, attempts=5),
                 lambda: until(lambda: route.ipv4_route_exist(R3, "11.0.13.0/24") is False, attempts=5),
                 lambda: until(lambda: route.ipv4_route_exist(R3, "11.0.14.0/24") is False, attempts=5),
                 lambda: until(lambda: route.ipv4_route_exist(R3, "11.0.15.0/24") is False, attempts=5))

    _, hport0 = env.ltop.xlate("PC", "data3")
    with infamy.IsolatedMacVlan(hport0) as ns0:
        with test.step("Testing connectivity through NSSA area, from PC:data3 to 11.0.8.1"):
            ns0.addip("192.168.3.2")
            ns0.addroute("0.0.0.0/0", "192.168.3.1")
            ns0.must_reach("11.0.8.1")

    _, hport0 = env.ltop.xlate("PC", "data4")
    with infamy.IsolatedMacVlan(hport0) as ns0:
        ns0.addip("192.168.4.2")
        ns0.addroute("0.0.0.0/0", "192.168.4.1")
        with test.step("Verify that the route to 10.0.0.3 from PC:data4, go through 10.0.41.2"):
            trace = ns0.traceroute("10.0.0.3")
            assert len(trace) == 3
            assert trace[1][1] == "10.0.41.2"
            assert trace[2][1] == "10.0.0.3"

        with test.step("Break link R1:ring2 --- R4:ring1"):
            # Here we should test with link breakers, to test BFD
            # recouppling, for now disable the link
            disable_link(R1, R1ring2)

        with test.step("Verify that the route to 10.0.0.3 from PC:data4, go through 10.0.24.1"):
            until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.24.1", proto="ietf-ospf:ospfv2"), attempts=100)
            until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2") is False, attempts=10)
            trace = ns0.traceroute("10.0.0.3")
            assert len(trace) == 3
            assert trace[1][1] == "10.0.24.1"
            assert trace[2][1] == "10.0.0.3"
            ns0.must_reach("10.0.0.3")

    test.succeed()
