From 65251ce80f02a61cfa06d752aa914bffd8493918 Mon Sep 17 00:00:00 2001 From: harios_niral Date: Tue, 18 Aug 2020 00:26:51 -0700 Subject: [PATCH 1/2] doc, yang, isisd : Support for different VRF in isisd 1. Added isis with different vrf and it's dependecies. 2. Added new vrf leaf in yang. 3. A minor change for IF_DOWN_FROM_Z passing argrument is replaced with ifp pointer in api "isis_if_delete_hook()". 4. Minor fix in the isisd spf unit test. Co-authored-by: Kaushik " Signed-off-by: harios_niral --- doc/figures/nodes.dot | 2 +- doc/user/isisd.rst | 27 +++++- isisd/isis_circuit.c | 39 +++++---- isisd/isis_cli.c | 160 +++++++++++++++++++++++++----------- isisd/isis_main.c | 5 +- isisd/isis_nb.c | 7 ++ isisd/isis_nb.h | 1 + isisd/isis_nb_config.c | 69 +++++++++++++--- isisd/isis_zebra.c | 14 ++++ isisd/isis_zebra.h | 1 + isisd/isisd.c | 136 +++++++++++++++++++++++++++--- isisd/isisd.h | 9 +- tests/isisd/test_isis_spf.c | 2 +- vtysh/vtysh.c | 5 +- yang/frr-isisd.yang | 15 +++- 15 files changed, 395 insertions(+), 97 deletions(-) diff --git a/doc/figures/nodes.dot b/doc/figures/nodes.dot index b548b5529a..4ce147b2c4 100644 --- a/doc/figures/nodes.dot +++ b/doc/figures/nodes.dot @@ -47,7 +47,7 @@ digraph climodes { CONFIG_NODE -> OSPF_NODE [ label="router ospf [(1-65535)] [vrf NAME]" ]; CONFIG_NODE -> OSPF6_NODE [ label="router ospf6" ]; CONFIG_NODE -> LDP_NODE [ label="mpls ldp" ]; - CONFIG_NODE -> ISIS_NODE [ label="router isis WORD" ]; + CONFIG_NODE -> ISIS_NODE [ label="router isis WORD [vrf NAME]" ]; CONFIG_NODE -> RMAP_NODE [ label="route-map WORD (1-65535)" ]; CONFIG_NODE -> PW_NODE [ label="pseudowire IFNAME" ]; CONFIG_NODE -> VTY_NODE [ label="line vty" ]; diff --git a/doc/user/isisd.rst b/doc/user/isisd.rst index 1155b49eb1..8cbbe0809f 100644 --- a/doc/user/isisd.rst +++ b/doc/user/isisd.rst @@ -33,8 +33,8 @@ ISIS router To start the ISIS process you have to specify the ISIS router. As of this writing, *isisd* does not support multiple ISIS processes. -.. index:: [no] router isis WORD -.. clicmd:: [no] router isis WORD +.. index:: [no] router isis WORD [vrf NAME] +.. clicmd:: [no] router isis WORD [vrf NAME] Enable or disable the ISIS process by specifying the ISIS domain with 'WORD'. *isisd* does not yet support multiple ISIS processes but you must @@ -202,8 +202,8 @@ ISIS interface .. _ip-router-isis-word: -.. index:: [no] router isis WORD -.. clicmd:: [no] router isis WORD +.. index:: [no] router isis WORD [vrf NAME] +.. clicmd:: [no] router isis WORD [vrf NAME] Activate ISIS adjacency on this interface. Note that the name of ISIS instance must be the same as the one used to configure the ISIS process (see @@ -751,3 +751,22 @@ A Segment Routing configuration, with IPv4, IPv6, SRGB and MSD configuration. segment-routing prefix 2001:db8:1000::1/128 index 101 explicit-null ! +ISIS Vrf Configuration Examples +=============================== + +A simple vrf example: + +.. code-block:: frr + + ! + interface eth0 vrf RED + ip router isis FOO vrf RED + isis network point-to-point + isis circuit-type level-2-only + ! + router isis FOO vrf RED + net 47.0023.0000.0000.0000.0000.0000.0000.1900.0004.00 + metric-style wide + is-type level-2-only + + diff --git a/isisd/isis_circuit.c b/isisd/isis_circuit.c index 985e07820f..1214c01a12 100644 --- a/isisd/isis_circuit.c +++ b/isisd/isis_circuit.c @@ -1244,21 +1244,24 @@ static int isis_interface_config_write(struct vty *vty) #else static int isis_interface_config_write(struct vty *vty) { - struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct vrf *vrf = NULL; int write = 0; - struct interface *ifp; - struct lyd_node *dnode; - FOR_ALL_INTERFACES (vrf, ifp) { - dnode = yang_dnode_get( - running_config->dnode, - "/frr-interface:lib/interface[name='%s'][vrf='%s']", - ifp->name, vrf->name); - if (dnode == NULL) - continue; + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + struct interface *ifp; - write++; - nb_cli_show_dnode_cmds(vty, dnode, false); + FOR_ALL_INTERFACES (vrf, ifp) { + struct lyd_node *dnode; + dnode = yang_dnode_get( + running_config->dnode, + "/frr-interface:lib/interface[name='%s'][vrf='%s']", + ifp->name, vrf->name); + if (dnode == NULL) + continue; + + write++; + nb_cli_show_dnode_cmds(vty, dnode, false); + } } return write; } @@ -1268,6 +1271,7 @@ struct isis_circuit *isis_circuit_create(struct isis_area *area, struct interface *ifp) { struct isis_circuit *circuit = circuit_scan_by_ifp(ifp); + if (circuit && circuit->area) return NULL; circuit = isis_csm_state_change(ISIS_ENABLE, circuit, area); @@ -1446,7 +1450,7 @@ int isis_if_delete_hook(struct interface *ifp) /* Clean up the circuit data */ if (ifp && ifp->info) { circuit = ifp->info; - isis_csm_state_change(IF_DOWN_FROM_Z, circuit, circuit->area); + isis_csm_state_change(IF_DOWN_FROM_Z, circuit, ifp); } return 0; @@ -1454,10 +1458,15 @@ int isis_if_delete_hook(struct interface *ifp) static int isis_ifp_create(struct interface *ifp) { - if (if_is_operative(ifp)) + struct vrf *vrf = NULL; + + if (if_is_operative(ifp)) { + vrf = vrf_lookup_by_id(ifp->vrf_id); + if (vrf) + isis_global_instance_create(vrf->name); isis_csm_state_change(IF_UP_FROM_Z, circuit_scan_by_ifp(ifp), ifp); - + } hook_call(isis_if_new_hook, ifp); return 0; diff --git a/isisd/isis_cli.c b/isisd/isis_cli.c index 4d02758003..31fe41db82 100644 --- a/isisd/isis_cli.c +++ b/isisd/isis_cli.c @@ -46,16 +46,21 @@ /* * XPath: /frr-isisd:isis/instance */ -DEFPY_YANG_NOSH(router_isis, router_isis_cmd, "router isis WORD$tag", - ROUTER_STR - "ISO IS-IS\n" - "ISO Routing area tag\n") +DEFPY_YANG_NOSH(router_isis, router_isis_cmd, + "router isis WORD$tag [vrf NAME$vrf_name]", + ROUTER_STR + "ISO IS-IS\n" + "ISO Routing area tag\n" VRF_CMD_HELP_STR) { int ret; char base_xpath[XPATH_MAXLEN]; + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + snprintf(base_xpath, XPATH_MAXLEN, - "/frr-isisd:isis/instance[area-tag='%s']", tag); + "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", tag, + vrf_name); nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); /* default value in yang for is-type is level-1, but in FRR * the first instance is assigned is-type level-1-2. We @@ -77,25 +82,30 @@ DEFPY_YANG_NOSH(router_isis, router_isis_cmd, "router isis WORD$tag", return ret; } -DEFPY_YANG(no_router_isis, no_router_isis_cmd, "no router isis WORD$tag", - NO_STR ROUTER_STR - "ISO IS-IS\n" - "ISO Routing area tag\n") +DEFPY_YANG(no_router_isis, no_router_isis_cmd, + "no router isis WORD$tag [vrf NAME$vrf_name]", + NO_STR ROUTER_STR + "ISO IS-IS\n" + "ISO Routing area tag\n" VRF_CMD_HELP_STR) { char temp_xpath[XPATH_MAXLEN]; struct listnode *node, *nnode; struct isis_circuit *circuit = NULL; struct isis_area *area = NULL; - if (!yang_dnode_exists(vty->candidate_config->dnode, - "/frr-isisd:isis/instance[area-tag='%s']", - tag)) { + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + + if (!yang_dnode_exists( + vty->candidate_config->dnode, + "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", tag, + vrf_name)) { vty_out(vty, "ISIS area %s not found.\n", tag); return CMD_ERR_NOTHING_TODO; } nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); - area = isis_area_lookup(tag, VRF_DEFAULT); + area = isis_area_lookup_by_vrf(tag, vrf_name); if (area && area->circuit_list && listcount(area->circuit_list)) { for (ALL_LIST_ELEMENTS(area->circuit_list, node, nnode, circuit)) { @@ -114,15 +124,23 @@ DEFPY_YANG(no_router_isis, no_router_isis_cmd, "no router isis WORD$tag", } return nb_cli_apply_changes( - vty, "/frr-isisd:isis/instance[area-tag='%s']", tag); + vty, "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", tag, + vrf_name); } void cli_show_router_isis(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { + const char *vrf = NULL; + + vrf = yang_dnode_get_string(dnode, "./vrf"); + vty_out(vty, "!\n"); - vty_out(vty, "router isis %s\n", + vty_out(vty, "router isis %s ", yang_dnode_get_string(dnode, "./area-tag")); + if (!strmatch(vrf, VRF_DEFAULT_NAME)) + vty_out(vty, "vrf %s", yang_dnode_get_string(dnode, "./vrf")); + vty_out(vty, "\n"); } /* @@ -131,16 +149,18 @@ void cli_show_router_isis(struct vty *vty, struct lyd_node *dnode, * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv6-routing * XPath: /frr-isisd:isis/instance */ -DEFPY_YANG(ip_router_isis, ip_router_isis_cmd, "ip router isis WORD$tag", - "Interface Internet Protocol config commands\n" - "IP router interface commands\n" - "IS-IS routing protocol\n" - "Routing process tag\n") +DEFPY_YANG(ip_router_isis, ip_router_isis_cmd, + "ip router isis WORD$tag [vrf NAME$vrf_name]", + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + "IS-IS routing protocol\n" + "Routing process tag\n" VRF_CMD_HELP_STR) { char temp_xpath[XPATH_MAXLEN]; const char *circ_type; struct isis_area *area = NULL; struct interface *ifp; + struct vrf *vrf; /* area will be created if it is not present. make sure the yang model * is synced with FRR and call the appropriate NB cb. @@ -150,16 +170,26 @@ DEFPY_YANG(ip_router_isis, ip_router_isis_cmd, "ip router isis WORD$tag", return CMD_SUCCESS; } ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false); - if (ifp) - area = isis_area_lookup(tag, ifp->vrf_id); + if (!vrf_name && ifp->vrf_id == VRF_DEFAULT) + vrf_name = VRF_DEFAULT_NAME; + + if (ifp->vrf_id != VRF_DEFAULT) { + vrf = vrf_lookup_by_id(ifp->vrf_id); + if (vrf && !vrf_name) + vrf_name = vrf->name; + } + area = isis_area_lookup_by_vrf(tag, vrf_name); if (!area) { + isis_global_instance_create(vrf_name); snprintf(temp_xpath, XPATH_MAXLEN, - "/frr-isisd:isis/instance[area-tag='%s']", tag); + "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", + tag, vrf_name); nb_cli_enqueue_change(vty, temp_xpath, NB_OP_CREATE, tag); - snprintf(temp_xpath, XPATH_MAXLEN, - "/frr-isisd:isis/instance[area-tag='%s']/is-type", - tag); + snprintf( + temp_xpath, XPATH_MAXLEN, + "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']/is-type", + tag, vrf_name); nb_cli_enqueue_change(vty, temp_xpath, NB_OP_MODIFY, listcount(im->isis) == 0 ? "level-1-2" : NULL); @@ -167,6 +197,9 @@ DEFPY_YANG(ip_router_isis, ip_router_isis_cmd, "ip router isis WORD$tag", NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY, tag); + + nb_cli_enqueue_change(vty, "./frr-isisd:isis/vrf", NB_OP_MODIFY, + vrf_name); nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv4-routing", NB_OP_MODIFY, "true"); nb_cli_enqueue_change( @@ -192,6 +225,9 @@ DEFPY_YANG(ip_router_isis, ip_router_isis_cmd, "ip router isis WORD$tag", NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY, tag); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/vrf", NB_OP_MODIFY, + vrf_name); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv4-routing", NB_OP_MODIFY, "true"); nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type", @@ -206,16 +242,18 @@ DEFPY_YANG(ip_router_isis, ip_router_isis_cmd, "ip router isis WORD$tag", return nb_cli_apply_changes(vty, NULL); } -DEFPY_YANG(ip6_router_isis, ip6_router_isis_cmd, "ipv6 router isis WORD$tag", - "Interface Internet Protocol config commands\n" - "IP router interface commands\n" - "IS-IS routing protocol\n" - "Routing process tag\n") +DEFPY_YANG(ip6_router_isis, ip6_router_isis_cmd, + "ipv6 router isis WORD$tag [vrf NAME$vrf_name]", + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + "IS-IS routing protocol\n" + "Routing process tag\n" VRF_CMD_HELP_STR) { char temp_xpath[XPATH_MAXLEN]; const char *circ_type; - struct isis_area *area = NULL; struct interface *ifp; + struct isis_area *area; + struct vrf *vrf; /* area will be created if it is not present. make sure the yang model * is synced with FRR and call the appropriate NB cb. @@ -225,16 +263,25 @@ DEFPY_YANG(ip6_router_isis, ip6_router_isis_cmd, "ipv6 router isis WORD$tag", return CMD_SUCCESS; ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false); - if (ifp) - area = isis_area_lookup(tag, ifp->vrf_id); + if (!vrf_name && ifp->vrf_id == VRF_DEFAULT) + vrf_name = VRF_DEFAULT_NAME; + if (ifp->vrf_id != VRF_DEFAULT) { + vrf = vrf_lookup_by_id(ifp->vrf_id); + if (vrf && !vrf_name) + vrf_name = vrf->name; + } + area = isis_area_lookup_by_vrf(tag, vrf_name); if (!area) { + isis_global_instance_create(vrf_name); snprintf(temp_xpath, XPATH_MAXLEN, - "/frr-isisd:isis/instance[area-tag='%s']", tag); + "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", + tag, vrf_name); nb_cli_enqueue_change(vty, temp_xpath, NB_OP_CREATE, tag); - snprintf(temp_xpath, XPATH_MAXLEN, - "/frr-isisd:isis/instance[area-tag='%s']/is-type", - tag); + snprintf( + temp_xpath, XPATH_MAXLEN, + "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']/is-type", + tag, vrf_name); nb_cli_enqueue_change(vty, temp_xpath, NB_OP_MODIFY, listcount(im->isis) == 0 ? "level-1-2" : NULL); @@ -242,6 +289,9 @@ DEFPY_YANG(ip6_router_isis, ip6_router_isis_cmd, "ipv6 router isis WORD$tag", NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY, tag); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/vrf", NB_OP_MODIFY, + vrf_name); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv6-routing", NB_OP_MODIFY, "true"); nb_cli_enqueue_change( @@ -267,6 +317,8 @@ DEFPY_YANG(ip6_router_isis, ip6_router_isis_cmd, "ipv6 router isis WORD$tag", NULL); nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY, tag); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/vrf", NB_OP_MODIFY, + vrf_name); nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv6-routing", NB_OP_MODIFY, "true"); nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type", @@ -282,13 +334,13 @@ DEFPY_YANG(ip6_router_isis, ip6_router_isis_cmd, "ipv6 router isis WORD$tag", } DEFPY_YANG(no_ip_router_isis, no_ip_router_isis_cmd, - "no $ip router isis [WORD]$tag", - NO_STR - "Interface Internet Protocol config commands\n" - "IP router interface commands\n" - "IP router interface commands\n" - "IS-IS routing protocol\n" - "Routing process tag\n") + "no $ip router isis [WORD]$tag [vrf NAME$vrf_name]", + NO_STR + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + "IP router interface commands\n" + "IS-IS routing protocol\n" + "Routing process tag\n") { const struct lyd_node *dnode; @@ -324,19 +376,33 @@ DEFPY_YANG(no_ip_router_isis, no_ip_router_isis_cmd, void cli_show_ip_isis_ipv4(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { + const char *vrf; + + vrf = yang_dnode_get_string(dnode, "../vrf"); + if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); - vty_out(vty, " ip router isis %s\n", + vty_out(vty, " ip router isis %s ", yang_dnode_get_string(dnode, "../area-tag")); + if (!strmatch(vrf, VRF_DEFAULT_NAME)) + vty_out(vty, "vrf %s", vrf); + vty_out(vty, "\n"); } void cli_show_ip_isis_ipv6(struct vty *vty, struct lyd_node *dnode, bool show_defaults) { + const char *vrf; + + vrf = yang_dnode_get_string(dnode, "../vrf"); + if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); - vty_out(vty, " ipv6 router isis %s\n", + vty_out(vty, " ipv6 router isis %s ", yang_dnode_get_string(dnode, "../area-tag")); + if (!strmatch(vrf, VRF_DEFAULT_NAME)) + vty_out(vty, "vrf %s", vrf); + vty_out(vty, "\n"); } /* diff --git a/isisd/isis_main.c b/isisd/isis_main.c index 6352303c23..26f5227aae 100644 --- a/isisd/isis_main.c +++ b/isisd/isis_main.c @@ -236,13 +236,12 @@ int main(int argc, char **argv, char **envp) /* thread master */ isis_master_init(frr_init()); master = im->master; - /* * initializations */ isis_error_init(); access_list_init(); - vrf_init(NULL, NULL, NULL, NULL, NULL); + isis_vrf_init(); prefix_list_init(); isis_init(); isis_circuit_init(); @@ -261,7 +260,7 @@ int main(int argc, char **argv, char **envp) mt_init(); /* create the global 'isis' instance */ - isis_global_instance_create(); + isis_global_instance_create(VRF_DEFAULT_NAME); isis_zebra_init(master, instance); isis_bfd_init(); diff --git a/isisd/isis_nb.c b/isisd/isis_nb.c index 2b8b02e3f1..33b0b4d02c 100644 --- a/isisd/isis_nb.c +++ b/isisd/isis_nb.c @@ -550,6 +550,13 @@ const struct frr_yang_module_info frr_isisd_info = { .modify = lib_interface_isis_area_tag_modify, }, }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/vrf", + .cbs = { + .modify = lib_interface_isis_vrf_modify, + }, + }, + { .xpath = "/frr-interface:lib/interface/frr-isisd:isis/circuit-type", .cbs = { diff --git a/isisd/isis_nb.h b/isisd/isis_nb.h index a9401bc86a..a79cb8ff57 100644 --- a/isisd/isis_nb.h +++ b/isisd/isis_nb.h @@ -168,6 +168,7 @@ int isis_instance_mpls_te_router_address_destroy( int lib_interface_isis_create(struct nb_cb_create_args *args); int lib_interface_isis_destroy(struct nb_cb_destroy_args *args); int lib_interface_isis_area_tag_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_vrf_modify(struct nb_cb_modify_args *args); int lib_interface_isis_ipv4_routing_modify(struct nb_cb_modify_args *args); int lib_interface_isis_ipv6_routing_modify(struct nb_cb_modify_args *args); int lib_interface_isis_circuit_type_modify(struct nb_cb_modify_args *args); diff --git a/isisd/isis_nb_config.c b/isisd/isis_nb_config.c index d722868414..170fe92c28 100644 --- a/isisd/isis_nb_config.c +++ b/isisd/isis_nb_config.c @@ -53,16 +53,19 @@ int isis_instance_create(struct nb_cb_create_args *args) { struct isis_area *area; const char *area_tag; + const char *vrf_name; if (args->event != NB_EV_APPLY) return NB_OK; - + vrf_name = yang_dnode_get_string(args->dnode, "./vrf"); area_tag = yang_dnode_get_string(args->dnode, "./area-tag"); - area = isis_area_lookup(area_tag, VRF_DEFAULT); + isis_global_instance_create(vrf_name); + area = isis_area_lookup_by_vrf(area_tag, vrf_name); if (area) return NB_ERR_INCONSISTENCY; - area = isis_area_create(area_tag, VRF_DEFAULT_NAME); + area = isis_area_create(area_tag, vrf_name); + /* save area in dnode to avoid looking it up all the time */ nb_running_set_entry(args->dnode, area); @@ -75,7 +78,6 @@ int isis_instance_destroy(struct nb_cb_destroy_args *args) if (args->event != NB_EV_APPLY) return NB_OK; - area = nb_running_unset_entry(args->dnode); isis_area_destroy(area); @@ -116,7 +118,6 @@ int isis_instance_area_address_create(struct nb_cb_create_args *args) area = nb_running_get_entry(args->dnode, NULL, true); if (area == NULL) return NB_ERR_VALIDATION; - addr.addr_len = dotformat2buff(buff, net_title); memcpy(addr.area_addr, buff, addr.addr_len); if (addr.area_addr[addr.addr_len - 1] != 0) { @@ -148,6 +149,7 @@ int isis_instance_area_address_create(struct nb_cb_create_args *args) case NB_EV_APPLY: area = nb_running_get_entry(args->dnode, NULL, true); addrr = args->resource->ptr; + assert(area); if (area->isis->sysid_set == 0) { /* @@ -1830,8 +1832,10 @@ int lib_interface_isis_create(struct nb_cb_create_args *args) { struct isis_area *area = NULL; struct interface *ifp; - struct isis_circuit *circuit; + struct isis_circuit *circuit = NULL; + struct vrf *vrf; const char *area_tag = yang_dnode_get_string(args->dnode, "./area-tag"); + const char *vrf_name = yang_dnode_get_string(args->dnode, "./vrf"); uint32_t min_mtu, actual_mtu; switch (args->event) { @@ -1846,8 +1850,17 @@ int lib_interface_isis_create(struct nb_cb_create_args *args) /* zebra might not know yet about the MTU - nothing we can do */ if (!ifp || ifp->mtu == 0) break; + vrf = vrf_lookup_by_id(ifp->vrf_id); + if (ifp->vrf_id != VRF_DEFAULT && vrf + && strcmp(vrf->name, vrf_name) != 0) { + snprintf(args->errmsg, args->errmsg_len, + "interface %s not in vrf %s\n", ifp->name, + vrf_name); + return NB_ERR_VALIDATION; + } actual_mtu = if_is_broadcast(ifp) ? ifp->mtu - LLC_LEN : ifp->mtu; + area = isis_area_lookup(area_tag, ifp->vrf_id); if (area) min_mtu = area->lsp_mtu; @@ -1866,9 +1879,7 @@ int lib_interface_isis_create(struct nb_cb_create_args *args) } break; case NB_EV_APPLY: - ifp = nb_running_get_entry(args->dnode, NULL, true); - if (ifp) - area = isis_area_lookup(area_tag, ifp->vrf_id); + area = isis_area_lookup_by_vrf(area_tag, vrf_name); /* The area should have already be created. We are * setting the priority of the global isis area creation * slightly lower, so it should be executed first, but I @@ -1881,7 +1892,7 @@ int lib_interface_isis_create(struct nb_cb_create_args *args) __func__, area_tag); abort(); } - + ifp = nb_running_get_entry(args->dnode, NULL, true); circuit = isis_circuit_create(area, ifp); assert(circuit && (circuit->state == C_STATE_CONF @@ -1956,6 +1967,44 @@ int lib_interface_isis_area_tag_modify(struct nb_cb_modify_args *args) return NB_OK; } +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/vrf + */ +int lib_interface_isis_vrf_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct vrf *vrf; + const char *ifname, *vrfname, *vrf_name; + struct isis_circuit *circuit; + + if (args->event == NB_EV_VALIDATE) { + /* libyang doesn't like relative paths across module boundaries + */ + ifname = yang_dnode_get_string(args->dnode->parent->parent, + "./name"); + vrfname = yang_dnode_get_string(args->dnode->parent->parent, + "./vrf"); + vrf = vrf_lookup_by_name(vrfname); + assert(vrf); + ifp = if_lookup_by_name(ifname, vrf->vrf_id); + + if (!ifp) + return NB_OK; + + vrf_name = yang_dnode_get_string(args->dnode, NULL); + circuit = circuit_scan_by_ifp(ifp); + if (circuit && circuit->area && circuit->area->isis + && strcmp(circuit->area->isis->name, vrf_name)) { + snprintf(args->errmsg, args->errmsg_len, + "ISIS circuit is already defined on vrf %s", + circuit->area->isis->name); + return NB_ERR_VALIDATION; + } + } + + return NB_OK; +} + /* * XPath: /frr-interface:lib/interface/frr-isisd:isis/circuit-type */ diff --git a/isisd/isis_zebra.c b/isisd/isis_zebra.c index 3aa21a9aed..a50eb607d9 100644 --- a/isisd/isis_zebra.c +++ b/isisd/isis_zebra.c @@ -577,6 +577,20 @@ int isis_zebra_label_manager_connect(void) return 0; } +void isis_zebra_vrf_register(struct isis *isis) +{ + if (!zclient || zclient->sock < 0 || !isis) + return; + + if (isis->vrf_id != VRF_UNKNOWN) { + if (IS_DEBUG_EVENTS) + zlog_debug("%s: Register VRF %s id %u", __func__, + isis->name, isis->vrf_id); + zclient_send_reg_requests(zclient, isis->vrf_id); + } +} + + static void isis_zebra_connected(struct zclient *zclient) { zclient_send_reg_requests(zclient, VRF_DEFAULT); diff --git a/isisd/isis_zebra.h b/isisd/isis_zebra.h index 4449b63c2e..768919ff46 100644 --- a/isisd/isis_zebra.h +++ b/isisd/isis_zebra.h @@ -57,5 +57,6 @@ bool isis_zebra_label_manager_ready(void); int isis_zebra_label_manager_connect(void); int isis_zebra_request_label_range(uint32_t base, uint32_t chunk_size); int isis_zebra_release_label_range(uint32_t start, uint32_t end); +void isis_zebra_vrf_register(struct isis *isis); #endif /* _ZEBRA_ISIS_ZEBRA_H */ diff --git a/isisd/isisd.c b/isisd/isisd.c index aca98bf651..2a2c71b1fd 100644 --- a/isisd/isisd.c +++ b/isisd/isisd.c @@ -35,6 +35,7 @@ #include "prefix.h" #include "table.h" #include "qobj.h" +#include "zclient.h" #include "vrf.h" #include "spf_backoff.h" #include "lib/northbound_cli.h" @@ -167,29 +168,32 @@ void isis_master_init(struct thread_master *master) im->master = master; } -void isis_global_instance_create() +void isis_global_instance_create(const char *vrf_name) { struct isis *isis; - isis = isis_lookup_by_vrfid(VRF_DEFAULT); + isis = isis_lookup_by_vrfname(vrf_name); if (isis == NULL) { - isis = isis_new(VRF_DEFAULT); + isis = isis_new(vrf_name); isis_add(isis); } } -struct isis *isis_new(vrf_id_t vrf_id) +struct isis *isis_new(const char *vrf_name) { struct vrf *vrf; struct isis *isis; isis = XCALLOC(MTYPE_ISIS, sizeof(struct isis)); - isis->vrf_id = vrf_id; - vrf = vrf_lookup_by_id(vrf_id); + vrf = vrf_lookup_by_name(vrf_name); if (vrf) { + isis->vrf_id = vrf->vrf_id; isis_vrf_link(isis, vrf); isis->name = XSTRDUP(MTYPE_ISIS, vrf->name); + } else { + isis->vrf_id = VRF_UNKNOWN; + isis->name = XSTRDUP(MTYPE_ISIS, vrf_name); } if (IS_DEBUG_EVENTS) @@ -223,15 +227,20 @@ struct isis_area *isis_area_create(const char *area_tag, const char *vrf_name) if (vrf) { isis = isis_lookup_by_vrfid(vrf->vrf_id); if (isis == NULL) { - isis = isis_new(vrf->vrf_id); + isis = isis_new(vrf_name); isis_add(isis); } - } else - return NULL; + } else { + isis = isis_lookup_by_vrfid(VRF_UNKNOWN); + if (isis == NULL) { + isis = isis_new(vrf_name); + isis_add(isis); + } + } } else { isis = isis_lookup_by_vrfid(VRF_DEFAULT); if (isis == NULL) { - isis = isis_new(VRF_DEFAULT); + isis = isis_new(VRF_DEFAULT_NAME); isis_add(isis); } } @@ -336,6 +345,24 @@ struct isis_area *isis_area_create(const char *area_tag, const char *vrf_name) return area; } +struct isis_area *isis_area_lookup_by_vrf(const char *area_tag, + const char *vrf_name) +{ + struct isis_area *area; + struct listnode *node; + struct isis *isis = NULL; + + isis = isis_lookup_by_vrfname(vrf_name); + if (isis == NULL) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) + if (strcmp(area->area_tag, area_tag) == 0) + return area; + + return NULL; +} + struct isis_area *isis_area_lookup(const char *area_tag, vrf_id_t vrf_id) { struct isis_area *area; @@ -449,6 +476,95 @@ void isis_area_destroy(struct isis_area *area) } +/* This is hook function for vrf create called as part of vrf_init */ +static int isis_vrf_new(struct vrf *vrf) +{ + if (IS_DEBUG_EVENTS) + zlog_debug("%s: VRF Created: %s(%u)", __func__, vrf->name, + vrf->vrf_id); + + return 0; +} + +/* This is hook function for vrf delete call as part of vrf_init */ +static int isis_vrf_delete(struct vrf *vrf) +{ + if (IS_DEBUG_EVENTS) + zlog_debug("%s: VRF Deletion: %s(%u)", __func__, vrf->name, + vrf->vrf_id); + + return 0; +} + +static int isis_vrf_enable(struct vrf *vrf) +{ + struct isis *isis; + vrf_id_t old_vrf_id; + + if (IS_DEBUG_EVENTS) + zlog_debug("%s: VRF %s id %u enabled", __func__, vrf->name, + vrf->vrf_id); + + isis = isis_lookup_by_vrfname(vrf->name); + if (isis) { + if (isis->name && strmatch(vrf->name, VRF_DEFAULT_NAME)) { + XFREE(MTYPE_ISIS, isis->name); + isis->name = NULL; + } + old_vrf_id = isis->vrf_id; + /* We have instance configured, link to VRF and make it "up". */ + isis_vrf_link(isis, vrf); + if (IS_DEBUG_EVENTS) + zlog_debug( + "%s: isis linked to vrf %s vrf_id %u (old id %u)", + __func__, vrf->name, isis->vrf_id, old_vrf_id); + if (old_vrf_id != isis->vrf_id) { + frr_with_privs (&isisd_privs) { + /* stop zebra redist to us for old vrf */ + zclient_send_dereg_requests(zclient, + old_vrf_id); + /* start zebra redist to us for new vrf */ + isis_zebra_vrf_register(isis); + } + } + } + + return 0; +} + +static int isis_vrf_disable(struct vrf *vrf) +{ + struct isis *isis; + vrf_id_t old_vrf_id = VRF_UNKNOWN; + + if (vrf->vrf_id == VRF_DEFAULT) + return 0; + + if (IS_DEBUG_EVENTS) + zlog_debug("%s: VRF %s id %d disabled.", __func__, vrf->name, + vrf->vrf_id); + isis = isis_lookup_by_vrfname(vrf->name); + if (isis) { + old_vrf_id = isis->vrf_id; + + /* We have instance configured, unlink + * from VRF and make it "down". + */ + isis_vrf_unlink(isis, vrf); + if (IS_DEBUG_EVENTS) + zlog_debug("%s: isis old_vrf_id %d unlinked", __func__, + old_vrf_id); + } + + return 0; +} + +void isis_vrf_init(void) +{ + vrf_init(isis_vrf_new, isis_vrf_enable, isis_vrf_disable, + isis_vrf_delete, isis_vrf_enable); +} + void isis_finish(struct isis *isis) { struct vrf *vrf = NULL; diff --git a/isisd/isisd.h b/isisd/isisd.h index 0c0a1eed10..c26a62dfac 100644 --- a/isisd/isisd.h +++ b/isisd/isisd.h @@ -73,7 +73,6 @@ struct isis_master { struct list *isis; /* ISIS thread master. */ struct thread_master *master; - /* Various OSPF global configuration. */ uint8_t options; }; #define F_ISIS_UNIT_TEST 0x01 @@ -213,15 +212,19 @@ void isis_finish(struct isis *isis); void isis_master_init(struct thread_master *master); void isis_vrf_link(struct isis *isis, struct vrf *vrf); void isis_vrf_unlink(struct isis *isis, struct vrf *vrf); -void isis_global_instance_create(void); +void isis_global_instance_create(const char *vrf_name); struct isis *isis_lookup_by_vrfid(vrf_id_t vrf_id); struct isis *isis_lookup_by_vrfname(const char *vrfname); struct isis *isis_lookup_by_sysid(const uint8_t *sysid); void isis_init(void); -struct isis *isis_new(vrf_id_t vrf_id); +void isis_vrf_init(void); + +struct isis *isis_new(const char *vrf_name); struct isis_area *isis_area_create(const char *, const char *); struct isis_area *isis_area_lookup(const char *, vrf_id_t vrf_id); +struct isis_area *isis_area_lookup_by_vrf(const char *area_tag, + const char *vrf_name); int isis_area_get(struct vty *vty, const char *area_tag); void isis_area_destroy(struct isis_area *area); void print_debug(struct vty *, int, int); diff --git a/tests/isisd/test_isis_spf.c b/tests/isisd/test_isis_spf.c index dae906b956..73bb531dc0 100644 --- a/tests/isisd/test_isis_spf.c +++ b/tests/isisd/test_isis_spf.c @@ -291,7 +291,7 @@ int main(int argc, char **argv) /* IS-IS inits. */ yang_module_load("frr-isisd"); - isis = isis_new(VRF_DEFAULT); + isis = isis_new(VRF_DEFAULT_NAME); listnode_add(im->isis, isis); SET_FLAG(im->options, F_ISIS_UNIT_TEST); debug_spf_events |= DEBUG_SPF_EVENTS; diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index d3fc8bc338..4edbb7a889 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -1892,10 +1892,11 @@ DEFUNSH(VTYSH_LDPD, ldp_member_pseudowire_ifname, } #endif -DEFUNSH(VTYSH_ISISD, router_isis, router_isis_cmd, "router isis WORD", +DEFUNSH(VTYSH_ISISD, router_isis, router_isis_cmd, + "router isis WORD [vrf NAME]", ROUTER_STR "ISO IS-IS\n" - "ISO Routing area tag\n") + "ISO Routing area tag\n" VRF_CMD_HELP_STR) { vty->node = ISIS_NODE; return CMD_SUCCESS; diff --git a/yang/frr-isisd.yang b/yang/frr-isisd.yang index 1bb693a1ef..00296516ef 100644 --- a/yang/frr-isisd.yang +++ b/yang/frr-isisd.yang @@ -340,6 +340,13 @@ module frr-isisd { "Area-tag associated to this circuit."; } + leaf vrf { + type string; + default "default"; + description + "VRF NAME."; + } + leaf ipv4-routing { type boolean; default "false"; @@ -793,7 +800,7 @@ module frr-isisd { description "Configuration of the IS-IS routing daemon."; list instance { - key "area-tag"; + key "area-tag vrf"; description "IS-IS routing instance."; leaf area-tag { @@ -802,6 +809,12 @@ module frr-isisd { "Area-tag associated to this routing instance."; } + leaf vrf { + type string; + description + "VRF NAME."; + } + leaf is-type { type level; default "level-1-2"; From 9375b5aa24b94ba49282c569c98d598a6ec8ed9c Mon Sep 17 00:00:00 2001 From: harios_niral Date: Tue, 1 Sep 2020 00:56:44 -0700 Subject: [PATCH 2/2] topotests : Topotest for different VRF in isisd 1. Topotest for isis-vrf is added for ipv4 and ipv6. 2. Test case for checking isis topology. 3. Test case for checking zebra isis routes. 4. Test case for checking linux vrf routes. 5. 2 new API's written in topotest/lib for checking vrf routes. Co-authored-by: Kaushik " Signed-off-by: harios_niral --- tests/topotests/isis-topo1-vrf/__init__.py | 0 tests/topotests/isis-topo1-vrf/r1/isisd.conf | 15 + .../topotests/isis-topo1-vrf/r1/r1_route.json | 57 +++ .../isis-topo1-vrf/r1/r1_route6.json | 40 ++ .../isis-topo1-vrf/r1/r1_route6_linux.json | 14 + .../isis-topo1-vrf/r1/r1_route_linux.json | 13 + .../isis-topo1-vrf/r1/r1_topology.json | 80 +++ tests/topotests/isis-topo1-vrf/r1/zebra.conf | 9 + tests/topotests/isis-topo1-vrf/r2/isisd.conf | 15 + .../topotests/isis-topo1-vrf/r2/r2_route.json | 57 +++ .../isis-topo1-vrf/r2/r2_route6.json | 40 ++ .../isis-topo1-vrf/r2/r2_route6_linux.json | 14 + .../isis-topo1-vrf/r2/r2_route_linux.json | 13 + .../isis-topo1-vrf/r2/r2_topology.json | 80 +++ tests/topotests/isis-topo1-vrf/r2/zebra.conf | 9 + tests/topotests/isis-topo1-vrf/r3/isisd.conf | 22 + .../topotests/isis-topo1-vrf/r3/r3_route.json | 112 +++++ .../isis-topo1-vrf/r3/r3_route6.json | 78 +++ .../isis-topo1-vrf/r3/r3_route6_linux.json | 26 + .../isis-topo1-vrf/r3/r3_route_linux.json | 24 + .../isis-topo1-vrf/r3/r3_topology.json | 132 +++++ tests/topotests/isis-topo1-vrf/r3/zebra.conf | 13 + tests/topotests/isis-topo1-vrf/r4/isisd.conf | 25 + .../topotests/isis-topo1-vrf/r4/r4_route.json | 105 ++++ .../isis-topo1-vrf/r4/r4_route6.json | 78 +++ .../isis-topo1-vrf/r4/r4_route6_linux.json | 26 + .../isis-topo1-vrf/r4/r4_route_linux.json | 24 + .../isis-topo1-vrf/r4/r4_topology.json | 132 +++++ tests/topotests/isis-topo1-vrf/r4/zebra.conf | 13 + tests/topotests/isis-topo1-vrf/r5/isisd.conf | 21 + .../topotests/isis-topo1-vrf/r5/r5_route.json | 106 ++++ .../isis-topo1-vrf/r5/r5_route6.json | 78 +++ .../isis-topo1-vrf/r5/r5_route6_linux.json | 26 + .../isis-topo1-vrf/r5/r5_route_linux.json | 24 + .../isis-topo1-vrf/r5/r5_topology.json | 124 +++++ tests/topotests/isis-topo1-vrf/r5/zebra.conf | 13 + .../isis-topo1-vrf/test_isis_topo1_vrf.dot | 100 ++++ .../isis-topo1-vrf/test_isis_topo1_vrf.jpg | Bin 0 -> 74340 bytes .../isis-topo1-vrf/test_isis_topo1_vrf.py | 455 ++++++++++++++++++ tests/topotests/lib/topotest.py | 84 ++++ 40 files changed, 2297 insertions(+) create mode 100644 tests/topotests/isis-topo1-vrf/__init__.py create mode 100755 tests/topotests/isis-topo1-vrf/r1/isisd.conf create mode 100644 tests/topotests/isis-topo1-vrf/r1/r1_route.json create mode 100644 tests/topotests/isis-topo1-vrf/r1/r1_route6.json create mode 100755 tests/topotests/isis-topo1-vrf/r1/r1_route6_linux.json create mode 100755 tests/topotests/isis-topo1-vrf/r1/r1_route_linux.json create mode 100644 tests/topotests/isis-topo1-vrf/r1/r1_topology.json create mode 100755 tests/topotests/isis-topo1-vrf/r1/zebra.conf create mode 100755 tests/topotests/isis-topo1-vrf/r2/isisd.conf create mode 100644 tests/topotests/isis-topo1-vrf/r2/r2_route.json create mode 100644 tests/topotests/isis-topo1-vrf/r2/r2_route6.json create mode 100755 tests/topotests/isis-topo1-vrf/r2/r2_route6_linux.json create mode 100755 tests/topotests/isis-topo1-vrf/r2/r2_route_linux.json create mode 100644 tests/topotests/isis-topo1-vrf/r2/r2_topology.json create mode 100755 tests/topotests/isis-topo1-vrf/r2/zebra.conf create mode 100755 tests/topotests/isis-topo1-vrf/r3/isisd.conf create mode 100644 tests/topotests/isis-topo1-vrf/r3/r3_route.json create mode 100644 tests/topotests/isis-topo1-vrf/r3/r3_route6.json create mode 100755 tests/topotests/isis-topo1-vrf/r3/r3_route6_linux.json create mode 100755 tests/topotests/isis-topo1-vrf/r3/r3_route_linux.json create mode 100644 tests/topotests/isis-topo1-vrf/r3/r3_topology.json create mode 100755 tests/topotests/isis-topo1-vrf/r3/zebra.conf create mode 100755 tests/topotests/isis-topo1-vrf/r4/isisd.conf create mode 100644 tests/topotests/isis-topo1-vrf/r4/r4_route.json create mode 100644 tests/topotests/isis-topo1-vrf/r4/r4_route6.json create mode 100755 tests/topotests/isis-topo1-vrf/r4/r4_route6_linux.json create mode 100755 tests/topotests/isis-topo1-vrf/r4/r4_route_linux.json create mode 100644 tests/topotests/isis-topo1-vrf/r4/r4_topology.json create mode 100755 tests/topotests/isis-topo1-vrf/r4/zebra.conf create mode 100755 tests/topotests/isis-topo1-vrf/r5/isisd.conf create mode 100644 tests/topotests/isis-topo1-vrf/r5/r5_route.json create mode 100644 tests/topotests/isis-topo1-vrf/r5/r5_route6.json create mode 100755 tests/topotests/isis-topo1-vrf/r5/r5_route6_linux.json create mode 100755 tests/topotests/isis-topo1-vrf/r5/r5_route_linux.json create mode 100644 tests/topotests/isis-topo1-vrf/r5/r5_topology.json create mode 100755 tests/topotests/isis-topo1-vrf/r5/zebra.conf create mode 100755 tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.dot create mode 100755 tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.jpg create mode 100755 tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.py diff --git a/tests/topotests/isis-topo1-vrf/__init__.py b/tests/topotests/isis-topo1-vrf/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/topotests/isis-topo1-vrf/r1/isisd.conf b/tests/topotests/isis-topo1-vrf/r1/isisd.conf new file mode 100755 index 0000000000..4ac4597015 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r1/isisd.conf @@ -0,0 +1,15 @@ +hostname r1 +debug isis adj-packets +debug isis events +debug isis update-packets +interface r1-eth0 + ip router isis 1 vrf r1-cust1 + ipv6 router isis 1 vrf r1-cust1 + isis circuit-type level-2-only +! +router isis 1 vrf r1-cust1 + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0000.00 + metric-style wide + redistribute ipv4 connected level-2 + redistribute ipv6 connected level-2 +! diff --git a/tests/topotests/isis-topo1-vrf/r1/r1_route.json b/tests/topotests/isis-topo1-vrf/r1/r1_route.json new file mode 100644 index 0000000000..790808f2cd --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r1/r1_route.json @@ -0,0 +1,57 @@ +{ + "10.0.10.0/24": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "active": true, + "afi": "ipv4", + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r1-eth0", + "ip": "10.0.20.1" + } + ], + "prefix": "10.0.10.0/24", + "protocol": "isis", + "selected": true, + "vrfId": 3, + "vrfName": "r1-cust1" + } + ], + "10.0.20.0/24": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "afi": "ipv4", + "interfaceIndex": 2, + "interfaceName": "r1-eth0", + "ip": "10.0.20.1" + } + ], + "prefix": "10.0.20.0/24", + "protocol": "isis", + "vrfId": 3, + "vrfName": "r1-cust1" + }, + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r1-eth0" + } + ], + "prefix": "10.0.20.0/24", + "protocol": "connected", + "selected": true, + "vrfId": 3, + "vrfName": "r1-cust1" + } + ] +} diff --git a/tests/topotests/isis-topo1-vrf/r1/r1_route6.json b/tests/topotests/isis-topo1-vrf/r1/r1_route6.json new file mode 100644 index 0000000000..332cbb3290 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r1/r1_route6.json @@ -0,0 +1,40 @@ +{ + "2001:db8:1:1::/64": [ + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r1-eth0" + } + ], + "prefix": "2001:db8:1:1::/64", + "protocol": "connected", + "selected": true, + "vrfId": 3, + "vrfName": "r1-cust1" + } + ], + "2001:db8:2:1::/64": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "active": true, + "afi": "ipv6", + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r1-eth0" + } + ], + "prefix": "2001:db8:2:1::/64", + "protocol": "isis", + "selected": true, + "vrfId": 3, + "vrfName": "r1-cust1" + } + ] +} diff --git a/tests/topotests/isis-topo1-vrf/r1/r1_route6_linux.json b/tests/topotests/isis-topo1-vrf/r1/r1_route6_linux.json new file mode 100755 index 0000000000..d1ace402ba --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r1/r1_route6_linux.json @@ -0,0 +1,14 @@ +{ + "2001:db8:1:1::/64": { + "dev": "r1-eth0", + "metric": "256", + "pref": "medium", + "proto": "kernel" + }, + "2001:db8:2:1::/64": { + "dev": "r1-eth0", + "metric": "20", + "pref": "medium", + "proto": "187" + } +} diff --git a/tests/topotests/isis-topo1-vrf/r1/r1_route_linux.json b/tests/topotests/isis-topo1-vrf/r1/r1_route_linux.json new file mode 100755 index 0000000000..6af22297e9 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r1/r1_route_linux.json @@ -0,0 +1,13 @@ +{ + "10.0.10.0/24": { + "dev": "r1-eth0", + "metric": "20", + "proto": "187", + "via": "10.0.20.1" + }, + "10.0.20.0/24": { + "dev": "r1-eth0", + "proto": "kernel", + "scope": "link" + } +} diff --git a/tests/topotests/isis-topo1-vrf/r1/r1_topology.json b/tests/topotests/isis-topo1-vrf/r1/r1_topology.json new file mode 100644 index 0000000000..8e3cdc7bd6 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r1/r1_topology.json @@ -0,0 +1,80 @@ +{ + "1": { + "level-1": { + "ipv4": [ + { + "vertex": "r1" + } + ], + "ipv6": [ + { + "vertex": "r1" + } + ] + }, + "level-2": { + "ipv4": [ + { + "vertex": "r1" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.20.0/24" + }, + { + "interface": "r1-eth0", + "metric": "10", + "next-hop": "r3", + "parent": "r1(4)", + "type": "TE-IS", + "vertex": "r3" + }, + { + "interface": "r3", + "metric": "TE", + "next-hop": "20", + "parent": "r1-eth0", + "type": "IP", + "vertex": "10.0.20.0/24" + }, + { + "interface": "r3", + "metric": "TE", + "next-hop": "20", + "parent": "r1-eth0", + "type": "IP", + "vertex": "10.0.10.0/24" + } + ], + "ipv6": [ + { + "vertex": "r1" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP6", + "vertex": "2001:db8:1:1::/64" + }, + { + "interface": "r1-eth0", + "metric": "10", + "next-hop": "r3", + "parent": "r1(4)", + "type": "TE-IS", + "vertex": "r3" + }, + { + "interface": "r3", + "metric": "internal", + "next-hop": "20", + "parent": "r1-eth0", + "type": "IP6", + "vertex": "2001:db8:2:1::/64" + } + ] + } + } +} diff --git a/tests/topotests/isis-topo1-vrf/r1/zebra.conf b/tests/topotests/isis-topo1-vrf/r1/zebra.conf new file mode 100755 index 0000000000..fa1c02e5f8 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r1/zebra.conf @@ -0,0 +1,9 @@ +hostname r1 +interface r1-eth0 vrf r1-cust1 + ip address 10.0.20.2/24 + ipv6 address 2001:db8:1:1::2/64 +! +interface lo + ip address 10.254.0.1/32 + ipv6 address 2001:db8:F::1/128 +! diff --git a/tests/topotests/isis-topo1-vrf/r2/isisd.conf b/tests/topotests/isis-topo1-vrf/r2/isisd.conf new file mode 100755 index 0000000000..4c68540265 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r2/isisd.conf @@ -0,0 +1,15 @@ +hostname r2 +debug isis adj-packets +debug isis events +debug isis update-packets +interface r2-eth0 + ip router isis 1 vrf r2-cust1 + ipv6 router isis 1 vrf r2-cust1 + isis circuit-type level-2-only +! +router isis 1 vrf r2-cust1 + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0001.00 + metric-style wide + redistribute ipv4 connected level-2 + redistribute ipv6 connected level-2 +! diff --git a/tests/topotests/isis-topo1-vrf/r2/r2_route.json b/tests/topotests/isis-topo1-vrf/r2/r2_route.json new file mode 100644 index 0000000000..b3ac86d218 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r2/r2_route.json @@ -0,0 +1,57 @@ +{ + "10.0.11.0/24": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "active": true, + "afi": "ipv4", + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r2-eth0", + "ip": "10.0.21.1" + } + ], + "prefix": "10.0.11.0/24", + "protocol": "isis", + "selected": true, + "vrfId": 3, + "vrfName": "r2-cust1" + } + ], + "10.0.21.0/24": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "afi": "ipv4", + "interfaceIndex": 2, + "interfaceName": "r2-eth0", + "ip": "10.0.21.1" + } + ], + "prefix": "10.0.21.0/24", + "protocol": "isis", + "vrfId": 3, + "vrfName": "r2-cust1" + }, + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r2-eth0" + } + ], + "prefix": "10.0.21.0/24", + "protocol": "connected", + "selected": true, + "vrfId": 3, + "vrfName": "r2-cust1" + } + ] +} diff --git a/tests/topotests/isis-topo1-vrf/r2/r2_route6.json b/tests/topotests/isis-topo1-vrf/r2/r2_route6.json new file mode 100644 index 0000000000..c8d11b4922 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r2/r2_route6.json @@ -0,0 +1,40 @@ +{ + "2001:db8:1:2::/64": [ + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r2-eth0" + } + ], + "prefix": "2001:db8:1:2::/64", + "protocol": "connected", + "selected": true, + "vrfId": 3, + "vrfName": "r2-cust1" + } + ], + "2001:db8:2:2::/64": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "active": true, + "afi": "ipv6", + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r2-eth0" + } + ], + "prefix": "2001:db8:2:2::/64", + "protocol": "isis", + "selected": true, + "vrfId": 3, + "vrfName": "r2-cust1" + } + ] +} diff --git a/tests/topotests/isis-topo1-vrf/r2/r2_route6_linux.json b/tests/topotests/isis-topo1-vrf/r2/r2_route6_linux.json new file mode 100755 index 0000000000..27423e1936 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r2/r2_route6_linux.json @@ -0,0 +1,14 @@ +{ + "2001:db8:1:2::/64": { + "dev": "r2-eth0", + "metric": "256", + "pref": "medium", + "proto": "kernel" + }, + "2001:db8:2:2::/64": { + "dev": "r2-eth0", + "metric": "20", + "pref": "medium", + "proto": "187" + } +} diff --git a/tests/topotests/isis-topo1-vrf/r2/r2_route_linux.json b/tests/topotests/isis-topo1-vrf/r2/r2_route_linux.json new file mode 100755 index 0000000000..744b0780f3 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r2/r2_route_linux.json @@ -0,0 +1,13 @@ +{ + "10.0.11.0/24": { + "dev": "r2-eth0", + "metric": "20", + "proto": "187", + "via": "10.0.21.1" + }, + "10.0.21.0/24": { + "dev": "r2-eth0", + "proto": "kernel", + "scope": "link" + } +} diff --git a/tests/topotests/isis-topo1-vrf/r2/r2_topology.json b/tests/topotests/isis-topo1-vrf/r2/r2_topology.json new file mode 100644 index 0000000000..72022a8167 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r2/r2_topology.json @@ -0,0 +1,80 @@ +{ + "1": { + "level-1": { + "ipv4": [ + { + "vertex": "r2" + } + ], + "ipv6": [ + { + "vertex": "r2" + } + ] + }, + "level-2": { + "ipv4": [ + { + "vertex": "r2" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.21.0/24" + }, + { + "interface": "r2-eth0", + "metric": "10", + "next-hop": "r4", + "parent": "r2(4)", + "type": "TE-IS", + "vertex": "r4" + }, + { + "interface": "r4", + "metric": "TE", + "next-hop": "20", + "parent": "r2-eth0", + "type": "IP", + "vertex": "10.0.21.0/24" + }, + { + "interface": "r4", + "metric": "TE", + "next-hop": "20", + "parent": "r2-eth0", + "type": "IP", + "vertex": "10.0.11.0/24" + } + ], + "ipv6": [ + { + "vertex": "r2" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP6", + "vertex": "2001:db8:1:2::/64" + }, + { + "interface": "r2-eth0", + "metric": "10", + "next-hop": "r4", + "parent": "r2(4)", + "type": "TE-IS", + "vertex": "r4" + }, + { + "interface": "r4", + "metric": "internal", + "next-hop": "20", + "parent": "r2-eth0", + "type": "IP6", + "vertex": "2001:db8:2:2::/64" + } + ] + } + } +} \ No newline at end of file diff --git a/tests/topotests/isis-topo1-vrf/r2/zebra.conf b/tests/topotests/isis-topo1-vrf/r2/zebra.conf new file mode 100755 index 0000000000..a62af1749e --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r2/zebra.conf @@ -0,0 +1,9 @@ +hostname r2 +interface r2-eth0 vrf r2-cust1 + ip address 10.0.21.2/24 + ipv6 address 2001:db8:1:2::2/64 +! +interface lo + ip address 10.254.0.2/32 + ipv6 address 2001:db8:F::2/128 +! diff --git a/tests/topotests/isis-topo1-vrf/r3/isisd.conf b/tests/topotests/isis-topo1-vrf/r3/isisd.conf new file mode 100755 index 0000000000..ca01876690 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r3/isisd.conf @@ -0,0 +1,22 @@ +hostname r3 +debug isis adj-packets +debug isis events +debug isis update-packets +interface r3-eth0 + ip router isis 1 vrf r3-cust1 + ipv6 router isis 1 vrf r3-cust1 + isis circuit-type level-2-only +! +interface r3-eth1 + ip router isis 1 vrf r3-cust1 + ipv6 router isis 1 vrf r3-cust1 + isis circuit-type level-1 +! +router isis 1 vrf r3-cust1 + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0002.00 + metric-style wide + redistribute ipv4 connected level-1 + redistribute ipv4 connected level-2 + redistribute ipv6 connected level-1 + redistribute ipv6 connected level-2 +! diff --git a/tests/topotests/isis-topo1-vrf/r3/r3_route.json b/tests/topotests/isis-topo1-vrf/r3/r3_route.json new file mode 100644 index 0000000000..709d6b9aeb --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r3/r3_route.json @@ -0,0 +1,112 @@ +{ + "10.0.10.0/24": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "afi": "ipv4", + "interfaceIndex": 3, + "interfaceName": "r3-eth1", + "ip": "10.0.10.1" + } + ], + "prefix": "10.0.10.0/24", + "protocol": "isis", + "vrfId": 4, + "vrfName": "r3-cust1" + }, + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r3-eth1" + } + ], + "prefix": "10.0.10.0/24", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r3-cust1" + } + ], + "10.0.11.0/24": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "active": true, + "afi": "ipv4", + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r3-eth1", + "ip": "10.0.10.1" + } + ], + "prefix": "10.0.11.0/24", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r3-cust1" + } + ], + "10.0.20.0/24": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "afi": "ipv4", + "interfaceIndex": 2, + "interfaceName": "r3-eth0", + "ip": "10.0.20.2" + } + ], + "prefix": "10.0.20.0/24", + "protocol": "isis", + "vrfId": 4, + "vrfName": "r3-cust1" + }, + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r3-eth0" + } + ], + "prefix": "10.0.20.0/24", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r3-cust1" + } + ], + "10.0.21.0/24": [ + { + "distance": 115, + "metric": 30, + "nexthops": [ + { + "active": true, + "afi": "ipv4", + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r3-eth1", + "ip": "10.0.10.1" + } + ], + "prefix": "10.0.21.0/24", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r3-cust1" + } + ] +} diff --git a/tests/topotests/isis-topo1-vrf/r3/r3_route6.json b/tests/topotests/isis-topo1-vrf/r3/r3_route6.json new file mode 100644 index 0000000000..3a7c3861fa --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r3/r3_route6.json @@ -0,0 +1,78 @@ +{ + "2001:db8:1:1::/64": [ + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r3-eth0" + } + ], + "prefix": "2001:db8:1:1::/64", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r3-cust1" + } + ], + "2001:db8:1:2::/64": [ + { + "distance": 115, + "metric": 30, + "nexthops": [ + { + "active": true, + "afi": "ipv6", + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r3-eth1" + } + ], + "prefix": "2001:db8:1:2::/64", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r3-cust1" + } + ], + "2001:db8:2:1::/64": [ + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r3-eth1" + } + ], + "prefix": "2001:db8:2:1::/64", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r3-cust1" + } + ], + "2001:db8:2:2::/64": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "active": true, + "afi": "ipv6", + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r3-eth1" + } + ], + "prefix": "2001:db8:2:2::/64", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r3-cust1" + } + ] +} diff --git a/tests/topotests/isis-topo1-vrf/r3/r3_route6_linux.json b/tests/topotests/isis-topo1-vrf/r3/r3_route6_linux.json new file mode 100755 index 0000000000..bc527d2e1e --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r3/r3_route6_linux.json @@ -0,0 +1,26 @@ +{ + "2001:db8:1:1::/64": { + "dev": "r3-eth0", + "metric": "256", + "pref": "medium", + "proto": "kernel" + }, + "2001:db8:1:2::/64": { + "dev": "r3-eth1", + "metric": "20", + "pref": "medium", + "proto": "187" + }, + "2001:db8:2:1::/64": { + "dev": "r3-eth1", + "metric": "256", + "pref": "medium", + "proto": "kernel" + }, + "2001:db8:2:2::/64": { + "dev": "r3-eth1", + "metric": "20", + "pref": "medium", + "proto": "187" + } +} diff --git a/tests/topotests/isis-topo1-vrf/r3/r3_route_linux.json b/tests/topotests/isis-topo1-vrf/r3/r3_route_linux.json new file mode 100755 index 0000000000..515d376475 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r3/r3_route_linux.json @@ -0,0 +1,24 @@ +{ + "10.0.10.0/24": { + "dev": "r3-eth1", + "proto": "kernel", + "scope": "link" + }, + "10.0.11.0/24": { + "dev": "r3-eth1", + "metric": "20", + "proto": "187", + "via": "10.0.10.1" + }, + "10.0.20.0/24": { + "dev": "r3-eth0", + "proto": "kernel", + "scope": "link" + }, + "10.0.21.0/24": { + "dev": "r3-eth1", + "metric": "20", + "proto": "187", + "via": "10.0.10.1" + } +} diff --git a/tests/topotests/isis-topo1-vrf/r3/r3_topology.json b/tests/topotests/isis-topo1-vrf/r3/r3_topology.json new file mode 100644 index 0000000000..62b895766e --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r3/r3_topology.json @@ -0,0 +1,132 @@ +{ + "1": { + "level-1": { + "ipv4": [ + { + "vertex": "r3" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.10.0/24" + }, + { + "interface": "r3-eth1", + "metric": "10", + "next-hop": "r5", + "parent": "r3(4)", + "type": "TE-IS", + "vertex": "r5" + }, + { + "interface": "r5", + "metric": "TE", + "next-hop": "20", + "parent": "r3-eth1", + "type": "IP", + "vertex": "10.0.10.0/24" + }, + { + "interface": "r5", + "metric": "TE", + "next-hop": "20", + "parent": "r3-eth1", + "type": "IP", + "vertex": "10.0.11.0/24" + }, + { + "interface": "r5", + "metric": "TE", + "next-hop": "30", + "parent": "r3-eth1", + "type": "IP", + "vertex": "10.0.21.0/24" + } + ], + "ipv6": [ + { + "vertex": "r3" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP6", + "vertex": "2001:db8:2:1::/64" + }, + { + "interface": "r3-eth1", + "metric": "10", + "next-hop": "r5", + "parent": "r3(4)", + "type": "TE-IS", + "vertex": "r5" + }, + { + "interface": "r5", + "metric": "internal", + "next-hop": "20", + "parent": "r3-eth1", + "type": "IP6", + "vertex": "2001:db8:2:2::/64" + }, + { + "interface": "r5", + "metric": "internal", + "next-hop": "30", + "parent": "r3-eth1", + "type": "IP6", + "vertex": "2001:db8:1:2::/64" + } + ] + }, + "level-2": { + "ipv4": [ + { + "vertex": "r3" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.20.0/24" + }, + { + "interface": "r3-eth0", + "metric": "10", + "next-hop": "r3", + "parent": "r3(4)", + "type": "TE-IS", + "vertex": "r3" + }, + { + "interface": "r3", + "metric": "TE", + "next-hop": "20", + "parent": "r3-eth0", + "type": "IP", + "vertex": "10.0.20.0/24" + } + ], + "ipv6": [ + { + "vertex": "r3" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP6", + "vertex": "2001:db8:1:1::/64" + }, + { + "interface": "r3-eth0", + "metric": "10", + "next-hop": "r3", + "parent": "r3(4)", + "type": "TE-IS", + "vertex": "r3" + } + ] + } + } +} diff --git a/tests/topotests/isis-topo1-vrf/r3/zebra.conf b/tests/topotests/isis-topo1-vrf/r3/zebra.conf new file mode 100755 index 0000000000..ac0b810fce --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r3/zebra.conf @@ -0,0 +1,13 @@ +hostname r3 +interface r3-eth0 vrf r3-cust1 + ip address 10.0.20.1/24 + ipv6 address 2001:db8:1:1::1/64 +! +interface r3-eth1 vrf r3-cust1 + ip address 10.0.10.2/24 + ipv6 address 2001:db8:2:1::2/64 +! +interface lo + ip address 10.254.0.3/32 + ipv6 address 2001:db8:F::3/128 +! diff --git a/tests/topotests/isis-topo1-vrf/r4/isisd.conf b/tests/topotests/isis-topo1-vrf/r4/isisd.conf new file mode 100755 index 0000000000..74b1603d85 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r4/isisd.conf @@ -0,0 +1,25 @@ +hostname r4 +debug isis adj-packets +debug isis events +debug isis update-packets +debug isis lsp-gen +debug isis lsp-sched + +interface r4-eth0 + ip router isis 1 vrf r4-cust1 + ipv6 router isis 1 vrf r4-cust1 + isis circuit-type level-2-only +! +interface r4-eth1 + ip router isis 1 vrf r4-cust1 + ipv6 router isis 1 vrf r4-cust1 + isis circuit-type level-1 +! +router isis 1 vrf r4-cust1 + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0004.00 + metric-style wide + redistribute ipv4 connected level-1 + redistribute ipv4 connected level-2 + redistribute ipv6 connected level-1 + redistribute ipv6 connected level-2 +! diff --git a/tests/topotests/isis-topo1-vrf/r4/r4_route.json b/tests/topotests/isis-topo1-vrf/r4/r4_route.json new file mode 100644 index 0000000000..c464607a2b --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r4/r4_route.json @@ -0,0 +1,105 @@ +{ + "10.0.10.0/24": [ + { + "metric": 20, + "nexthops": [ + { + "active": true, + "afi": "ipv4", + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r4-eth1", + "ip": "10.0.11.1" + } + ], + "prefix": "10.0.10.0/24", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r4-cust1" + } + ], + "10.0.11.0/24": [ + { + "nexthops": [ + { + "afi": "ipv4", + "interfaceIndex": 3, + "interfaceName": "r4-eth1", + "ip": "10.0.11.1" + } + ], + "prefix": "10.0.11.0/24", + "protocol": "isis", + "vrfId": 4, + "vrfName": "r4-cust1" + }, + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r4-eth1" + } + ], + "prefix": "10.0.11.0/24", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r4-cust1" + } + ], + "10.0.20.0/24": [ + { + "nexthops": [ + { + "active": true, + "afi": "ipv4", + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r4-eth1", + "ip": "10.0.11.1" + } + ], + "prefix": "10.0.20.0/24", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r4-cust1" + } + ], + "10.0.21.0/24": [ + { + "nexthops": [ + { + "afi": "ipv4", + "interfaceIndex": 2, + "interfaceName": "r4-eth0", + "ip": "10.0.21.2" + } + ], + "prefix": "10.0.21.0/24", + "protocol": "isis", + "vrfId": 4, + "vrfName": "r4-cust1" + }, + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r4-eth0" + } + ], + "prefix": "10.0.21.0/24", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r4-cust1" + } + ] +} diff --git a/tests/topotests/isis-topo1-vrf/r4/r4_route6.json b/tests/topotests/isis-topo1-vrf/r4/r4_route6.json new file mode 100644 index 0000000000..8d3ea570f0 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r4/r4_route6.json @@ -0,0 +1,78 @@ +{ + "2001:db8:1:1::/64": [ + { + "distance": 115, + "metric": 30, + "nexthops": [ + { + "active": true, + "afi": "ipv6", + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r4-eth1" + } + ], + "prefix": "2001:db8:1:1::/64", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r4-cust1" + } + ], + "2001:db8:1:2::/64": [ + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r4-eth0" + } + ], + "prefix": "2001:db8:1:2::/64", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r4-cust1" + } + ], + "2001:db8:2:1::/64": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "active": true, + "afi": "ipv6", + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r4-eth1" + } + ], + "prefix": "2001:db8:2:1::/64", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r4-cust1" + } + ], + "2001:db8:2:2::/64": [ + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r4-eth1" + } + ], + "prefix": "2001:db8:2:2::/64", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r4-cust1" + } + ] +} diff --git a/tests/topotests/isis-topo1-vrf/r4/r4_route6_linux.json b/tests/topotests/isis-topo1-vrf/r4/r4_route6_linux.json new file mode 100755 index 0000000000..b1cd5b9db9 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r4/r4_route6_linux.json @@ -0,0 +1,26 @@ +{ + "2001:db8:1:1::/64": { + "dev": "r4-eth1", + "metric": "20", + "pref": "medium", + "proto": "187" + }, + "2001:db8:1:2::/64": { + "dev": "r4-eth0", + "metric": "256", + "pref": "medium", + "proto": "kernel" + }, + "2001:db8:2:1::/64": { + "dev": "r4-eth1", + "metric": "20", + "pref": "medium", + "proto": "187" + }, + "2001:db8:2:2::/64": { + "dev": "r4-eth1", + "metric": "256", + "pref": "medium", + "proto": "kernel" + } +} diff --git a/tests/topotests/isis-topo1-vrf/r4/r4_route_linux.json b/tests/topotests/isis-topo1-vrf/r4/r4_route_linux.json new file mode 100755 index 0000000000..3198b85789 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r4/r4_route_linux.json @@ -0,0 +1,24 @@ +{ + "10.0.10.0/24": { + "dev": "r4-eth1", + "metric": "20", + "proto": "187", + "via": "10.0.11.1" + }, + "10.0.11.0/24": { + "dev": "r4-eth1", + "proto": "kernel", + "scope": "link" + }, + "10.0.20.0/24": { + "dev": "r4-eth1", + "metric": "20", + "proto": "187", + "via": "10.0.11.1" + }, + "10.0.21.0/24": { + "dev": "r4-eth0", + "proto": "kernel", + "scope": "link" + } +} diff --git a/tests/topotests/isis-topo1-vrf/r4/r4_topology.json b/tests/topotests/isis-topo1-vrf/r4/r4_topology.json new file mode 100644 index 0000000000..0d69550cad --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r4/r4_topology.json @@ -0,0 +1,132 @@ +{ + "1": { + "level-1": { + "ipv4": [ + { + "vertex": "r4" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.11.0/24" + }, + { + "interface": "r4-eth1", + "metric": "10", + "next-hop": "r5", + "parent": "r4(4)", + "type": "TE-IS", + "vertex": "r5" + }, + { + "interface": "r5", + "metric": "TE", + "next-hop": "20", + "parent": "r4-eth1", + "type": "IP", + "vertex": "10.0.10.0/24" + }, + { + "interface": "r5", + "metric": "TE", + "next-hop": "20", + "parent": "r4-eth1", + "type": "IP", + "vertex": "10.0.11.0/24" + }, + { + "interface": "r5", + "metric": "TE", + "next-hop": "30", + "parent": "r4-eth1", + "type": "IP", + "vertex": "10.0.20.0/24" + } + ], + "ipv6": [ + { + "vertex": "r4" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP6", + "vertex": "2001:db8:2:2::/64" + }, + { + "interface": "r4-eth1", + "metric": "10", + "next-hop": "r5", + "parent": "r4(4)", + "type": "TE-IS", + "vertex": "r5" + }, + { + "interface": "r5", + "metric": "internal", + "next-hop": "20", + "parent": "r4-eth1", + "type": "IP6", + "vertex": "2001:db8:2:1::/64" + }, + { + "interface": "r5", + "metric": "internal", + "next-hop": "30", + "parent": "r4-eth1", + "type": "IP6", + "vertex": "2001:db8:1:1::/64" + } + ] + }, + "level-2": { + "ipv4": [ + { + "vertex": "r4" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.21.0/24" + }, + { + "interface": "r4-eth0", + "metric": "10", + "next-hop": "r2", + "parent": "r4(4)", + "type": "TE-IS", + "vertex": "r2" + }, + { + "interface": "r2", + "metric": "TE", + "next-hop": "20", + "parent": "r4-eth0", + "type": "IP", + "vertex": "10.0.21.0/24" + } + ], + "ipv6": [ + { + "vertex": "r4" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP6", + "vertex": "2001:db8:1:2::/64" + }, + { + "interface": "r4-eth0", + "metric": "10", + "next-hop": "r2", + "parent": "r4(4)", + "type": "TE-IS", + "vertex": "r2" + } + ] + } + } +} diff --git a/tests/topotests/isis-topo1-vrf/r4/zebra.conf b/tests/topotests/isis-topo1-vrf/r4/zebra.conf new file mode 100755 index 0000000000..9c8941f7a5 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r4/zebra.conf @@ -0,0 +1,13 @@ +hostname r4 +interface r4-eth0 vrf r4-cust1 + ip address 10.0.21.1/24 + ipv6 address 2001:db8:1:2::1/64 +! +interface r4-eth1 vrf r4-cust1 + ip address 10.0.11.2/24 + ipv6 address 2001:db8:2:2::2/64 +! +interface lo + ip address 10.254.0.4/32 + ipv6 address 2001:db8:F::4/128 +! diff --git a/tests/topotests/isis-topo1-vrf/r5/isisd.conf b/tests/topotests/isis-topo1-vrf/r5/isisd.conf new file mode 100755 index 0000000000..9e9b030455 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r5/isisd.conf @@ -0,0 +1,21 @@ +hostname r5 +debug isis adj-packets +debug isis events +debug isis update-packets +interface r5-eth0 + ip router isis 1 vrf r5-cust1 + ipv6 router isis 1 vrf r5-cust1 + isis circuit-type level-1 +! +interface r5-eth1 + ip router isis 1 vrf r5-cust1 + ipv6 router isis 1 vrf r5-cust1 + isis circuit-type level-1 +! +router isis 1 vrf r5-cust1 + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0005.00 + metric-style wide + is-type level-1 + redistribute ipv4 connected level-1 + redistribute ipv6 connected level-1 +! diff --git a/tests/topotests/isis-topo1-vrf/r5/r5_route.json b/tests/topotests/isis-topo1-vrf/r5/r5_route.json new file mode 100644 index 0000000000..58aee5ddcc --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r5/r5_route.json @@ -0,0 +1,106 @@ +{ + "10.0.10.0/24": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "afi": "ipv4", + "interfaceIndex": 2, + "interfaceName": "r5-eth0", + "ip": "10.0.10.2" + } + ], + "prefix": "10.0.10.0/24", + "protocol": "isis", + "vrfId": 4, + "vrfName": "r5-cust1" + }, + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r5-eth0" + } + ], + "prefix": "10.0.10.0/24", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r5-cust1" + } + ], + "10.0.11.0/24": [ + { + "nexthops": [ + { + "afi": "ipv4", + "interfaceIndex": 3, + "interfaceName": "r5-eth1", + "ip": "10.0.11.2" + } + ], + "prefix": "10.0.11.0/24", + "protocol": "isis", + "vrfId": 4, + "vrfName": "r5-cust1" + }, + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r5-eth1" + } + ], + "prefix": "10.0.11.0/24", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r5-cust1" + } + ], + "10.0.20.0/24": [ + { + "nexthops": [ + { + "active": true, + "afi": "ipv4", + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r5-eth0", + "ip": "10.0.10.2" + } + ], + "prefix": "10.0.20.0/24", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r5-cust1" + } + ], + "10.0.21.0/24": [ + { + "nexthops": [ + { + "active": true, + "afi": "ipv4", + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r5-eth1", + "ip": "10.0.11.2" + } + ], + "prefix": "10.0.21.0/24", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r5-cust1" + } + ] +} diff --git a/tests/topotests/isis-topo1-vrf/r5/r5_route6.json b/tests/topotests/isis-topo1-vrf/r5/r5_route6.json new file mode 100644 index 0000000000..e32bbcc2c1 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r5/r5_route6.json @@ -0,0 +1,78 @@ +{ + "2001:db8:1:1::/64": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "active": true, + "afi": "ipv6", + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r5-eth0" + } + ], + "prefix": "2001:db8:1:1::/64", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r5-cust1" + } + ], + "2001:db8:1:2::/64": [ + { + "distance": 115, + "metric": 20, + "nexthops": [ + { + "active": true, + "afi": "ipv6", + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r5-eth1" + } + ], + "prefix": "2001:db8:1:2::/64", + "protocol": "isis", + "selected": true, + "vrfId": 4, + "vrfName": "r5-cust1" + } + ], + "2001:db8:2:1::/64": [ + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 2, + "interfaceName": "r5-eth0" + } + ], + "prefix": "2001:db8:2:1::/64", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r5-cust1" + } + ], + "2001:db8:2:2::/64": [ + { + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "fib": true, + "interfaceIndex": 3, + "interfaceName": "r5-eth1" + } + ], + "prefix": "2001:db8:2:2::/64", + "protocol": "connected", + "selected": true, + "vrfId": 4, + "vrfName": "r5-cust1" + } + ] +} diff --git a/tests/topotests/isis-topo1-vrf/r5/r5_route6_linux.json b/tests/topotests/isis-topo1-vrf/r5/r5_route6_linux.json new file mode 100755 index 0000000000..3db3c93ea6 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r5/r5_route6_linux.json @@ -0,0 +1,26 @@ +{ + "2001:db8:1:1::/64": { + "dev": "r5-eth0", + "metric": "20", + "pref": "medium", + "proto": "187" + }, + "2001:db8:1:2::/64": { + "dev": "r5-eth1", + "metric": "20", + "pref": "medium", + "proto": "187" + }, + "2001:db8:2:1::/64": { + "dev": "r5-eth0", + "metric": "256", + "pref": "medium", + "proto": "kernel" + }, + "2001:db8:2:2::/64": { + "dev": "r5-eth1", + "metric": "256", + "pref": "medium", + "proto": "kernel" + } +} diff --git a/tests/topotests/isis-topo1-vrf/r5/r5_route_linux.json b/tests/topotests/isis-topo1-vrf/r5/r5_route_linux.json new file mode 100755 index 0000000000..6a38ba864a --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r5/r5_route_linux.json @@ -0,0 +1,24 @@ +{ + "10.0.10.0/24": { + "dev": "r5-eth0", + "proto": "kernel", + "scope": "link" + }, + "10.0.11.0/24": { + "dev": "r5-eth1", + "proto": "kernel", + "scope": "link" + }, + "10.0.20.0/24": { + "dev": "r5-eth0", + "metric": "20", + "proto": "187", + "via": "10.0.10.2" + }, + "10.0.21.0/24": { + "dev": "r5-eth1", + "metric": "20", + "proto": "187", + "via": "10.0.11.2" + } +} diff --git a/tests/topotests/isis-topo1-vrf/r5/r5_topology.json b/tests/topotests/isis-topo1-vrf/r5/r5_topology.json new file mode 100644 index 0000000000..b4ed6a069d --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r5/r5_topology.json @@ -0,0 +1,124 @@ +{ + "1": { + "level-1": { + "ipv4": [ + { + "vertex": "r5" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.10.0/24" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.11.0/24" + }, + { + "interface": "r5-eth0", + "metric": "10", + "next-hop": "r3", + "parent": "r5(4)", + "type": "TE-IS", + "vertex": "r3" + }, + { + "interface": "r5-eth1", + "metric": "10", + "next-hop": "r4", + "parent": "r5(4)", + "type": "TE-IS", + "vertex": "r4" + }, + { + "interface": "r3", + "metric": "TE", + "next-hop": "20", + "parent": "r5-eth0", + "type": "IP", + "vertex": "10.0.20.0/24" + }, + { + "interface": "r3", + "metric": "TE", + "next-hop": "20", + "parent": "r5-eth0", + "type": "IP", + "vertex": "10.0.10.0/24" + }, + { + "interface": "r4", + "metric": "TE", + "next-hop": "20", + "parent": "r5-eth1", + "type": "IP", + "vertex": "10.0.21.0/24" + }, + { + "interface": "r4", + "metric": "TE", + "next-hop": "20", + "parent": "r5-eth1", + "type": "IP", + "vertex": "10.0.11.0/24" + } + ], + "ipv6": [ + { + "vertex": "r5" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP6", + "vertex": "2001:db8:2:1::/64" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP6", + "vertex": "2001:db8:2:2::/64" + }, + { + "interface": "r5-eth0", + "metric": "10", + "next-hop": "r3", + "parent": "r5(4)", + "type": "TE-IS", + "vertex": "r3" + }, + { + "interface": "r5-eth1", + "metric": "10", + "next-hop": "r4", + "parent": "r5(4)", + "type": "TE-IS", + "vertex": "r4" + }, + { + "interface": "r3", + "metric": "internal", + "next-hop": "20", + "parent": "r5-eth0", + "type": "IP6", + "vertex": "2001:db8:1:1::/64" + }, + { + "interface": "r4", + "metric": "internal", + "next-hop": "20", + "parent": "r5-eth1", + "type": "IP6", + "vertex": "2001:db8:1:2::/64" + } + ] + }, + "level-2": { + "ipv4": [], + "ipv6": [] + } + } +} \ No newline at end of file diff --git a/tests/topotests/isis-topo1-vrf/r5/zebra.conf b/tests/topotests/isis-topo1-vrf/r5/zebra.conf new file mode 100755 index 0000000000..c6bc6302fc --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/r5/zebra.conf @@ -0,0 +1,13 @@ +hostname r5 +interface r5-eth0 vrf r5-cust1 + ip address 10.0.10.1/24 + ipv6 address 2001:db8:2:1::1/64 +! +interface r5-eth1 vrf r5-cust1 + ip address 10.0.11.1/24 + ipv6 address 2001:db8:2:2::1/64 +! +interface lo + ip address 10.254.0.5/32 + ipv6 address 2001:db8:F::5/128 +! diff --git a/tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.dot b/tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.dot new file mode 100755 index 0000000000..01f9ba780f --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.dot @@ -0,0 +1,100 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="isis topo1"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1\n10.254.0.1\n2001:DB8:F::1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2\n10.254.0.2\n2001:DB8:F::2", + fillcolor="#f08080", + style=filled, + ]; + r3 [ + shape=doubleoctagon + label="r3\n10.254.0.3\n2001:DB8:F::3", + fillcolor="#f08080", + style=filled, + ]; + r4 [ + shape=doubleoctagon + label="r4\n10.254.0.4\n2001:DB8:F::4", + fillcolor="#f08080", + style=filled, + ]; + r5 [ + shape=doubleoctagon + label="r5\n10.254.0.5\n2001:DB8:F::5", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n10.0.20.0/24\n2001:DB8:1:1::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw2 [ + shape=oval, + label="sw2\n10.0.21.0/24\n2001:DB8:1:2::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw3 [ + shape=oval, + label="sw3\n10.0.10.0/24\n2001:DB8:2:1::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw4 [ + shape=oval, + label="sw4\n10.0.11.0/24\n2001:DB8:2:2::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + subgraph cluster0 { + label="level 2"; + + r1 -- sw1 [label="eth0\n.2"]; + r2 -- sw2 [label="eth0\n.2"]; + } + + subgraph cluster1 { + label="level 1/2"; + + r3 -- sw1 [label="eth0\n.1"]; + r3 -- sw3 [label="eth1\n.2"]; + + r4 -- sw4 [label="eth1\n.2"]; + r4 -- sw2 [label="eth0\n.1"]; + } + + subgraph cluster2 { + label="level 1"; + + r5 -- sw3 [label="eth0\n.1"]; + r5 -- sw4 [label="eth1\n.1"]; + } +} diff --git a/tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.jpg b/tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.jpg new file mode 100755 index 0000000000000000000000000000000000000000..4ad730f2a08b66280530051b95f5deaf303ec34a GIT binary patch literal 74340 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 E3-H>x_5c6? literal 0 HcmV?d00001 diff --git a/tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.py b/tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.py new file mode 100755 index 0000000000..a0e34b71b0 --- /dev/null +++ b/tests/topotests/isis-topo1-vrf/test_isis_topo1_vrf.py @@ -0,0 +1,455 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2020 by Niral Networks, Inc. ("Niral Networks") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +test_isis_topo1_vrf.py: Test ISIS vrf topology. +""" + +import collections +import functools +import json +import os +import re +import sys +import pytest +import platform + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +from mininet.topo import Topo + + +class ISISTopo1(Topo): + "Simple two layer ISIS vrf topology" + + def build(self, *_args, **_opts): + "Build function" + tgen = get_topogen(self) + + # Add ISIS routers: + # r1 r2 + # | sw1 | sw2 + # r3 r4 + # | | + # sw3 sw4 + # \ / + # r5 + for routern in range(1, 6): + tgen.add_router("r{}".format(routern)) + + # r1 <- sw1 -> r3 + sw = tgen.add_switch("sw1") + sw.add_link(tgen.gears["r1"]) + sw.add_link(tgen.gears["r3"]) + + # r2 <- sw2 -> r4 + sw = tgen.add_switch("sw2") + sw.add_link(tgen.gears["r2"]) + sw.add_link(tgen.gears["r4"]) + + # r3 <- sw3 -> r5 + sw = tgen.add_switch("sw3") + sw.add_link(tgen.gears["r3"]) + sw.add_link(tgen.gears["r5"]) + + # r4 <- sw4 -> r5 + sw = tgen.add_switch("sw4") + sw.add_link(tgen.gears["r4"]) + sw.add_link(tgen.gears["r5"]) + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(ISISTopo1, mod.__name__) + tgen.start_topology() + + logger.info("Testing with VRF Lite support") + krel = platform.release() + + # May need to adjust handling of vrf traffic depending on kernel version + l3mdev_accept = 0 + if ( + topotest.version_cmp(krel, "4.15") >= 0 + and topotest.version_cmp(krel, "4.18") <= 0 + ): + l3mdev_accept = 1 + + if topotest.version_cmp(krel, "5.0") >= 0: + l3mdev_accept = 1 + + logger.info( + "krel '{0}' setting net.ipv4.tcp_l3mdev_accept={1}".format(krel, l3mdev_accept) + ) + + cmds = [ + "ip link add {0}-cust1 type vrf table 1001", + "ip link add loop1 type dummy", + "ip link set {0}-eth0 master {0}-cust1", + "ip link set {0}-eth1 master {0}-cust1", + ] + + # For all registered routers, load the zebra configuration file + for rname, router in tgen.routers().iteritems(): + # create VRF rx-cust1 and link rx-eth0 to rx-cust1 + for cmd in cmds: + output = tgen.net[rname].cmd(cmd.format(rname)) + output = tgen.net[rname].cmd("sysctl -n net.ipv4.tcp_l3mdev_accept") + logger.info( + "router {0}: existing tcp_l3mdev_accept was {1}".format(rname, output) + ) + + if l3mdev_accept: + output = tgen.net[rname].cmd( + "sysctl -w net.ipv4.tcp_l3mdev_accept={}".format(l3mdev_accept) + ) + + for rname, router in tgen.routers().iteritems(): + router.load_config( + TopoRouter.RD_ZEBRA, + os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_ISIS, + os.path.join(CWD, "{}/isisd.conf".format(rname)) + ) + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + + has_version_20 = False + for router in tgen.routers().values(): + if router.has_version("<", "4"): + has_version_20 = True + + if has_version_20: + logger.info("Skipping ISIS vrf tests for FRR 2.0") + tgen.set_error("ISIS has convergence problems with IPv6") + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + # move back rx-eth0 to default VRF + # delete rx-vrf + tgen.stop_topology() + +def test_isis_convergence(): + "Wait for the protocol to converge before starting to test" + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for ISIS protocol to converge") + + for rname, router in tgen.routers().iteritems(): + filename = "{0}/{1}/{1}_topology.json".format(CWD, rname) + expected = json.loads(open(filename).read()) + def compare_isis_topology(router, expected): + "Helper function to test ISIS vrf topology convergence." + actual = show_isis_topology(router) + + return topotest.json_cmp(actual, expected) + + test_func = functools.partial(compare_isis_topology, router, expected) + (result, diff) = topotest.run_and_expect(test_func, None, wait=0.5, count=120) + assert result, "ISIS did not converge on {}:\n{}".format(rname, diff) + +def test_isis_route_installation(): + "Check whether all expected routes are present" + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking routers for installed ISIS vrf routes") + # Check for routes in 'show ip route vrf {}-cust1 json' + for rname, router in tgen.routers().iteritems(): + filename = "{0}/{1}/{1}_route.json".format(CWD, rname) + expected = json.loads(open(filename, "r").read()) + actual = router.vtysh_cmd("show ip route vrf {0}-cust1 json".format(rname) , isjson=True) + # Older FRR versions don't list interfaces in some ISIS routes + if router.has_version("<", "3.1"): + for network, routes in expected.iteritems(): + for route in routes: + if route["protocol"] != "isis": + continue + + for nexthop in route["nexthops"]: + nexthop.pop("interfaceIndex", None) + nexthop.pop("interfaceName", None) + + assertmsg = "Router '{}' routes mismatch".format(rname) + assert topotest.json_cmp(actual, expected) is None, assertmsg + + +def test_isis_linux_route_installation(): + + dist = platform.dist() + + if (dist[1] == "16.04"): + pytest.skip("Kernel not supported for vrf") + + "Check whether all expected routes are present and installed in the OS" + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking routers for installed ISIS vrf routes in OS") + # Check for routes in `ip route show vrf {}-cust1` + for rname, router in tgen.routers().iteritems(): + filename = "{0}/{1}/{1}_route_linux.json".format(CWD, rname) + expected = json.loads(open(filename, "r").read()) + actual = topotest.ip4_vrf_route(router) + + # Older FRR versions install routes using different proto + if router.has_version("<", "3.1"): + for network, netoptions in expected.iteritems(): + if "proto" in netoptions and netoptions["proto"] == "187": + netoptions["proto"] = "zebra" + + assertmsg = "Router '{}' OS routes mismatch".format(rname) + assert topotest.json_cmp(actual, expected) is None, assertmsg + +def test_isis_route6_installation(): + "Check whether all expected routes are present" + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking routers for installed ISIS vrf IPv6 routes") + # Check for routes in 'show ipv6 route vrf {}-cust1 json' + for rname, router in tgen.routers().iteritems(): + filename = "{0}/{1}/{1}_route6.json".format(CWD, rname) + expected = json.loads(open(filename, "r").read()) + actual = router.vtysh_cmd("show ipv6 route vrf {}-cust1 json".format(rname) , isjson=True) + + # Older FRR versions don't list interfaces in some ISIS routes + if router.has_version("<", "3.1"): + for network, routes in expected.iteritems(): + for route in routes: + if route["protocol"] != "isis": + continue + + for nexthop in route["nexthops"]: + nexthop.pop("interfaceIndex", None) + nexthop.pop("interfaceName", None) + + assertmsg = "Router '{}' routes mismatch".format(rname) + assert topotest.json_cmp(actual, expected) is None, assertmsg + +def test_isis_linux_route6_installation(): + + dist = platform.dist() + + if (dist[1] == "16.04"): + pytest.skip("Kernel not supported for vrf") + + "Check whether all expected routes are present and installed in the OS" + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking routers for installed ISIS vrf IPv6 routes in OS") + # Check for routes in `ip -6 route show vrf {}-cust1` + for rname, router in tgen.routers().iteritems(): + filename = "{0}/{1}/{1}_route6_linux.json".format(CWD, rname) + expected = json.loads(open(filename, "r").read()) + actual = topotest.ip6_vrf_route(router) + + # Older FRR versions install routes using different proto + if router.has_version("<", "3.1"): + for network, netoptions in expected.iteritems(): + if "proto" in netoptions and netoptions["proto"] == "187": + netoptions["proto"] = "zebra" + + assertmsg = "Router '{}' OS routes mismatch".format(rname) + assert topotest.json_cmp(actual, expected) is None, assertmsg + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) + + +# +# Auxiliary functions +# + + +def dict_merge(dct, merge_dct): + """ + Recursive dict merge. Inspired by :meth:``dict.update()``, instead of + updating only top-level keys, dict_merge recurses down into dicts nested + to an arbitrary depth, updating keys. The ``merge_dct`` is merged into + ``dct``. + :param dct: dict onto which the merge is executed + :param merge_dct: dct merged into dct + :return: None + + Source: + https://gist.github.com/angstwad/bf22d1822c38a92ec0a9 + """ + for k, v in merge_dct.iteritems(): + if ( + k in dct + and isinstance(dct[k], dict) + and isinstance(merge_dct[k], collections.Mapping) + ): + dict_merge(dct[k], merge_dct[k]) + else: + dct[k] = merge_dct[k] + + +def parse_topology(lines, level): + """ + Parse the output of 'show isis topology level-X' into a Python dict. + """ + areas = {} + area = None + ipv = None + + for line in lines: + area_match = re.match(r"Area (.+):", line) + if area_match: + area = area_match.group(1) + if area not in areas: + areas[area] = {level: {"ipv4": [], "ipv6": []}} + ipv = None + continue + elif area is None: + continue + + if re.match(r"IS\-IS paths to level-. routers that speak IPv6", line): + ipv = "ipv6" + continue + if re.match(r"IS\-IS paths to level-. routers that speak IP", line): + ipv = "ipv4" + continue + + item_match = re.match(r"([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)", line) + if item_match is not None: + # Skip header + if ( + item_match.group(1) == "Vertex" + and item_match.group(2) == "Type" + and item_match.group(3) == "Metric" + and item_match.group(4) == "Next-Hop" + and item_match.group(5) == "Interface" + and item_match.group(6) == "Parent" + ): + continue + + areas[area][level][ipv].append( + { + "vertex": item_match.group(1), + "type": item_match.group(2), + "metric": item_match.group(3), + "next-hop": item_match.group(4), + "interface": item_match.group(5), + "parent": item_match.group(6), + } + ) + continue + + item_match = re.match(r"([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)", line) + if item_match is not None: + areas[area][level][ipv].append( + { + "vertex": item_match.group(1), + "type": item_match.group(2), + "metric": item_match.group(3), + "parent": item_match.group(4), + } + ) + continue + + item_match = re.match(r"([^ ]+)", line) + if item_match is not None: + areas[area][level][ipv].append({"vertex": item_match.group(1)}) + continue + + return areas + + +def show_isis_topology(router): + """ + Get the ISIS vrf topology in a dictionary format. + + Sample: + { + 'area-name': { + 'level-1': [ + { + 'vertex': 'r1' + } + ], + 'level-2': [ + { + 'vertex': '10.0.0.1/24', + 'type': 'IP', + 'parent': '0', + 'metric': 'internal' + } + ] + }, + 'area-name-2': { + 'level-2': [ + { + "interface": "rX-ethY", + "metric": "Z", + "next-hop": "rA", + "parent": "rC(B)", + "type": "TE-IS", + "vertex": "rD" + } + ] + } + } + """ + l1out = topotest.normalize_text( + router.vtysh_cmd("show isis vrf {}-cust1 topology level-1".format(router.name)) + ).splitlines() + l2out = topotest.normalize_text( + router.vtysh_cmd("show isis vrf {}-cust1 topology level-2".format(router.name)) + ).splitlines() + + l1 = parse_topology(l1out, "level-1") + l2 = parse_topology(l2out, "level-2") + + dict_merge(l1, l2) + return l1 + diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 766ab71ed1..5acec83b6e 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -721,6 +721,49 @@ def ip4_route(node): return result +def ip4_vrf_route(node): + """ + Gets a structured return of the command 'ip route show vrf {0}-cust1'. + It can be used in conjuction with json_cmp() to provide accurate assert explanations. + + Return example: + { + '10.0.1.0/24': { + 'dev': 'eth0', + 'via': '172.16.0.1', + 'proto': '188', + }, + '10.0.2.0/24': { + 'dev': 'eth1', + 'proto': 'kernel', + } + } + """ + output = normalize_text( + node.run("ip route show vrf {0}-cust1".format(node.name))).splitlines() + + result = {} + for line in output: + columns = line.split(" ") + route = result[columns[0]] = {} + prev = None + for column in columns: + if prev == "dev": + route["dev"] = column + if prev == "via": + route["via"] = column + if prev == "proto": + # translate protocol names back to numbers + route["proto"] = proto_name_to_number(column) + if prev == "metric": + route["metric"] = column + if prev == "scope": + route["scope"] = column + prev = column + + return result + + def ip6_route(node): """ Gets a structured return of the command 'ip -6 route'. It can be used in @@ -761,6 +804,47 @@ def ip6_route(node): return result +def ip6_vrf_route(node): + """ + Gets a structured return of the command 'ip -6 route show vrf {0}-cust1'. + It can be used in conjuction with json_cmp() to provide accurate assert explanations. + + Return example: + { + '2001:db8:1::/64': { + 'dev': 'eth0', + 'proto': '188', + }, + '2001:db8:2::/64': { + 'dev': 'eth1', + 'proto': 'kernel', + } + } + """ + output = normalize_text( + node.run("ip -6 route show vrf {0}-cust1".format(node.name))).splitlines() + result = {} + for line in output: + columns = line.split(" ") + route = result[columns[0]] = {} + prev = None + for column in columns: + if prev == "dev": + route["dev"] = column + if prev == "via": + route["via"] = column + if prev == "proto": + # translate protocol names back to numbers + route["proto"] = proto_name_to_number(column) + if prev == "metric": + route["metric"] = column + if prev == "pref": + route["pref"] = column + prev = column + + return result + + def ip_rules(node): """ Gets a structured return of the command 'ip rule'. It can be used in