summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/bgpd/test_mp_attr.c4
-rw-r--r--tests/isisd/test_common.c29
-rw-r--r--tests/isisd/test_isis_spf.c30
-rw-r--r--tests/lib/cxxcompat.c1
-rw-r--r--tests/lib/subdir.am2
-rw-r--r--tests/lib/test_typelist.h5
-rw-r--r--tests/topotests/all_protocol_startup/r1/ospf6d.conf-pre-v48
-rw-r--r--tests/topotests/all_protocol_startup/test_all_protocol_startup.py11
-rw-r--r--tests/topotests/bfd_topo3/r5/bfdd.conf6
-rw-r--r--tests/topotests/bfd_topo3/r6/bfdd.conf6
-rw-r--r--tests/topotests/bgp_accept_own/ce1/bgpd.conf2
-rw-r--r--tests/topotests/bgp_accept_own/ce2/bgpd.conf2
-rw-r--r--tests/topotests/bgp_accept_own/pe1/bgpd.conf8
-rw-r--r--tests/topotests/bgp_accept_own/rr1/bgpd.conf2
-rw-r--r--tests/topotests/bgp_bfd_down_cease_notification/r1/bgpd.conf2
-rw-r--r--tests/topotests/bgp_bfd_down_cease_notification/r2/bgpd.conf1
-rw-r--r--tests/topotests/bgp_bfd_down_cease_notification/test_bgp_bfd_down_cease_notification.py5
-rw-r--r--tests/topotests/bgp_comm_list_match/r2/bgpd.conf2
-rw-r--r--tests/topotests/bgp_confed1/r1/bgpd.conf8
-rw-r--r--tests/topotests/bgp_confed1/r2/bgpd.conf8
-rw-r--r--tests/topotests/bgp_confed1/r3/bgpd.conf8
-rw-r--r--tests/topotests/bgp_confed1/r4/bgpd.conf8
-rw-r--r--tests/topotests/bgp_dont_capability_negotiate/r1/bgpd.conf2
-rwxr-xr-xtests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py50
-rwxr-xr-xtests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py18
-rw-r--r--tests/topotests/bgp_features/r1/ospf_neighbor.json4
-rw-r--r--tests/topotests/bgp_features/r2/ospf_neighbor.json4
-rw-r--r--tests/topotests/bgp_features/r3/ospf_neighbor.json4
-rw-r--r--tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-3.py39
-rw-r--r--tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-4.py9
-rw-r--r--tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_up.py19
-rw-r--r--tests/topotests/bgp_labeled_unicast_addpath/test_bgp_labeled_unicast_addpath.py56
-rw-r--r--tests/topotests/bgp_lu_explicitnull/r1/bgpd.conf15
-rw-r--r--tests/topotests/bgp_lu_explicitnull/r1/zebra.conf6
-rw-r--r--tests/topotests/bgp_lu_explicitnull/r2/bgpd.conf15
-rw-r--r--tests/topotests/bgp_lu_explicitnull/r2/zebra.conf6
-rw-r--r--tests/topotests/bgp_lu_explicitnull/test_bgp_lu_explicitnull.py196
-rw-r--r--tests/topotests/bgp_node_target_extcommunities/__init__.py0
-rw-r--r--tests/topotests/bgp_node_target_extcommunities/r1/frr.conf21
-rw-r--r--tests/topotests/bgp_node_target_extcommunities/r2/frr.conf8
-rw-r--r--tests/topotests/bgp_node_target_extcommunities/r3/frr.conf8
-rw-r--r--tests/topotests/bgp_node_target_extcommunities/r4/frr.conf8
-rw-r--r--tests/topotests/bgp_node_target_extcommunities/test_bgp_node_target_extcommunities.py132
-rw-r--r--tests/topotests/bgp_prefix_list_any/r2/bgpd.conf2
-rw-r--r--tests/topotests/bgp_route_map_delay_timer/__init__.py0
-rw-r--r--tests/topotests/bgp_route_map_delay_timer/r1/bgpd.conf24
-rw-r--r--tests/topotests/bgp_route_map_delay_timer/r1/zebra.conf4
-rw-r--r--tests/topotests/bgp_route_map_delay_timer/r2/bgpd.conf4
-rw-r--r--tests/topotests/bgp_route_map_delay_timer/r2/zebra.conf4
-rw-r--r--tests/topotests/bgp_route_map_delay_timer/test_bgp_route_map_delay_timer.py120
-rw-r--r--tests/topotests/bgp_route_map_vpn_import/r1/bgpd.conf10
-rw-r--r--tests/topotests/bgp_route_origin_parser/pe1/bgpd.conf2
-rw-r--r--tests/topotests/bgp_route_origin_parser/test_bgp_route_origin_parser.py129
-rw-r--r--tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf2
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce1/bgpd.conf8
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce1/ipv6_rib.json58
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce1/zebra.conf16
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce2/bgpd.conf8
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce2/ipv6_rib.json58
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce2/zebra.conf16
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce3/bgpd.conf8
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce3/ipv6_rib.json58
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce3/zebra.conf14
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce4/bgpd.conf8
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce4/ipv6_rib.json58
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce4/zebra.conf14
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce5/bgpd.conf8
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce5/ipv6_rib.json58
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce5/zebra.conf14
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce6/bgpd.conf8
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce6/ipv6_rib.json58
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/ce6/zebra.conf14
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/bgpd.conf79
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json169
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_no_sid_rib.json23
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_sid_rib.json53
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_no_sid_rib.json23
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_sid_rib.json54
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_no_sid_rib.json77
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_sid_rib.json107
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_no_sid_rib.json77
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_sid_rib.json106
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_auto_sid_rib.json54
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_manual_sid_rib.json53
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_auto_sid_rib.json106
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_manual_sid_rib.json106
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_auto_no_sid_rib.json77
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_manual_no_sid_rib.json22
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_rib.json112
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/vrf20_rib.json106
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r1/zebra.conf43
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r2/bgpd.conf80
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json169
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r2/vrf10_rib.json106
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r2/vrf20_rib.json112
-rw-r--r--tests/topotests/bgp_srv6l3vpn_sid/r2/zebra.conf43
-rwxr-xr-xtests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py453
-rw-r--r--tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/zebra.conf6
-rw-r--r--tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/zebra.conf6
-rw-r--r--tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/zebra.conf6
-rw-r--r--tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/zebra.conf6
-rw-r--r--tests/topotests/bgp_suppress_fib/r2/bgpd.allowas_in.conf12
-rw-r--r--tests/topotests/bgp_suppress_fib/r2/bgpd.conf8
-rw-r--r--tests/topotests/bgp_unique_rid/test_bgp_unique_rid.py2
-rw-r--r--tests/topotests/bgp_vrf_md5_peering/r1/bgpd.conf2
-rw-r--r--tests/topotests/config_timing/test_config_timing.py4
-rwxr-xr-xtests/topotests/conftest.py200
-rw-r--r--tests/topotests/cspf_topo1/reference/sharp-ted.json28
-rw-r--r--tests/topotests/example_munet/munet.yaml17
-rw-r--r--tests/topotests/example_munet/r1/daemons6
-rw-r--r--tests/topotests/example_munet/r1/frr.conf7
-rw-r--r--tests/topotests/example_munet/r1/vtysh.conf1
-rw-r--r--tests/topotests/example_munet/r2/daemons6
-rw-r--r--tests/topotests/example_munet/r2/frr.conf10
-rw-r--r--tests/topotests/example_munet/r2/vtysh.conf1
-rw-r--r--tests/topotests/example_munet/r3/daemons6
-rw-r--r--tests/topotests/example_munet/r3/frr.conf7
-rw-r--r--tests/topotests/example_munet/r3/vtysh.conf1
-rw-r--r--tests/topotests/example_munet/test_munet.py10
-rw-r--r--tests/topotests/isis_advertise_high_metrics/test_isis_advertise_high_metrics.py40
-rw-r--r--tests/topotests/isis_snmp/r5/ldpdconf8
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/isisd.conf96
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step1/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step1/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step10/show_isis_flex_algo.ref126
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step10/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step11/show_isis_flex_algo.ref126
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step11/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step2/show_isis_flex_algo.ref126
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step2/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step3/show_isis_flex_algo.ref115
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step3/show_mpls_table.ref450
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step4/show_isis_flex_algo.ref126
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step4/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step5/show_isis_flex_algo.ref126
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step5/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step6/show_isis_flex_algo.ref112
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step6/show_mpls_table.ref450
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step7/show_isis_flex_algo.ref108
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step7/show_mpls_table.ref450
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step8/show_isis_flex_algo.ref126
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step8/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step9/show_isis_flex_algo.ref126
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/step9/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt1/zebra.conf39
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/isisd.conf96
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step1/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step1/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step10/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step10/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step11/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step11/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step2/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step2/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step3/show_isis_flex_algo.ref114
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step3/show_mpls_table.ref450
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step4/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step4/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step5/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step5/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step6/show_isis_flex_algo.ref111
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step6/show_mpls_table.ref450
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step7/show_isis_flex_algo.ref107
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step7/show_mpls_table.ref450
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step8/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step8/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step9/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/step9/show_mpls_table.ref482
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt2/zebra.conf39
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/isisd.conf76
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step1/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step1/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step10/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step10/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step11/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step11/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step2/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step2/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step3/show_isis_flex_algo.ref114
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step3/show_mpls_table.ref450
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step4/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step4/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step5/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step5/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step6/show_isis_flex_algo.ref111
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step6/show_mpls_table.ref450
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step7/show_isis_flex_algo.ref107
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step7/show_mpls_table.ref450
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step8/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step8/show_mpls_table.ref514
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step9/show_isis_flex_algo.ref125
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/step9/show_mpls_table.ref482
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo1/rt3/zebra.conf39
-rwxr-xr-xtests/topotests/isis_sr_flex_algo_topo1/test_isis_sr_flex_algo_topo1.py583
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/README.md8
-rwxr-xr-xtests/topotests/isis_sr_flex_algo_topo2/configure-te.sh46
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt0/bgpd.conf17
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt0/isisd.conf56
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt0/pathd.conf20
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt0/step1/route.json438
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt0/zebra.conf34
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt1/isisd.conf60
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt1/step1/route.json428
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt1/zebra.conf40
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt2/isisd.conf54
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt2/step1/route.json408
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt2/zebra.conf37
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt3/isisd.conf60
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt3/step1/route.json428
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt3/zebra.conf40
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt4/isisd.conf52
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt4/step1/route.json286
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt4/zebra.conf31
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt5/isisd.conf60
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt5/step1/route.json428
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt5/zebra.conf40
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt6/isisd.conf54
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt6/step1/route.json408
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt6/zebra.conf37
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt7/isisd.conf60
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt7/step1/route.json428
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt7/zebra.conf40
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt8/isisd.conf50
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt8/step1/route.json286
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt8/zebra.conf29
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt9/bgpd.conf17
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt9/isisd.conf56
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt9/pathd.conf20
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt9/step1/route.json438
-rw-r--r--tests/topotests/isis_sr_flex_algo_topo2/rt9/zebra.conf34
-rwxr-xr-xtests/topotests/isis_sr_flex_algo_topo2/test_isis_sr_flex_algo_topo2.py187
-rw-r--r--tests/topotests/isis_te_topo1/reference/ted_step1.json28
-rw-r--r--tests/topotests/isis_te_topo1/reference/ted_step10.json32
-rw-r--r--tests/topotests/isis_te_topo1/reference/ted_step2.json20
-rw-r--r--tests/topotests/isis_te_topo1/reference/ted_step3.json24
-rw-r--r--tests/topotests/isis_te_topo1/reference/ted_step4.json24
-rw-r--r--tests/topotests/isis_te_topo1/reference/ted_step5.json32
-rw-r--r--tests/topotests/isis_te_topo1/reference/ted_step6.json32
-rw-r--r--tests/topotests/isis_te_topo1/reference/ted_step7.json32
-rw-r--r--tests/topotests/isis_te_topo1/reference/ted_step8.json32
-rw-r--r--tests/topotests/kinds.yaml30
-rw-r--r--tests/topotests/ldp_oc_acl_topo1/r1/show_ip_ospf_neighbor.json4
-rw-r--r--tests/topotests/ldp_oc_acl_topo1/r2/show_ip_ospf_neighbor.json12
-rw-r--r--tests/topotests/ldp_oc_acl_topo1/r3/show_ip_ospf_neighbor.json8
-rw-r--r--tests/topotests/ldp_oc_acl_topo1/r4/show_ip_ospf_neighbor.json8
-rw-r--r--tests/topotests/ldp_oc_topo1/r1/show_ip_ospf_neighbor.json4
-rw-r--r--tests/topotests/ldp_oc_topo1/r2/show_ip_ospf_neighbor.json12
-rw-r--r--tests/topotests/ldp_oc_topo1/r3/show_ip_ospf_neighbor.json8
-rw-r--r--tests/topotests/ldp_oc_topo1/r4/show_ip_ospf_neighbor.json8
-rw-r--r--tests/topotests/ldp_sync_ospf_topo1/r1/show_ip_ospf_neighbor.json20
-rw-r--r--tests/topotests/ldp_sync_ospf_topo1/r2/show_ip_ospf_neighbor.json20
-rw-r--r--tests/topotests/ldp_sync_ospf_topo1/r3/show_ip_ospf_neighbor.json20
-rw-r--r--tests/topotests/ldp_topo1/r1/show_ip_ospf_neighbor.json10
-rw-r--r--tests/topotests/ldp_topo1/r2/show_ip_ospf_neighbor.json40
-rw-r--r--tests/topotests/ldp_topo1/r3/show_ip_ospf_neighbor.json30
-rw-r--r--tests/topotests/ldp_topo1/r4/show_ip_ospf_neighbor.json20
-rw-r--r--tests/topotests/ldp_vpls_topo1/r1/show_ip_ospf_neighbor.json20
-rw-r--r--tests/topotests/ldp_vpls_topo1/r2/show_ip_ospf_neighbor.json20
-rw-r--r--tests/topotests/ldp_vpls_topo1/r3/show_ip_ospf_neighbor.json20
-rw-r--r--tests/topotests/lib/common_config.py119
-rwxr-xr-xtests/topotests/lib/grpc-query.py3
-rw-r--r--tests/topotests/lib/micronet.py1018
-rw-r--r--tests/topotests/lib/micronet_cli.py306
-rw-r--r--tests/topotests/lib/micronet_compat.py363
-rw-r--r--tests/topotests/lib/ospf.py17
-rw-r--r--tests/topotests/lib/pim.py69
-rw-r--r--tests/topotests/lib/topogen.py71
-rw-r--r--tests/topotests/lib/topotest.py268
-rw-r--r--tests/topotests/mgmt_tests/test_yang_mgmt.py18
-rw-r--r--tests/topotests/multicast_mld_join_topo1/multicast_mld_local_join.json249
-rw-r--r--tests/topotests/multicast_mld_join_topo1/test_multicast_mld_local_join.py915
-rw-r--r--tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm1.py8
-rw-r--r--tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm2.py8
-rwxr-xr-xtests/topotests/multicast_pim6_static_rp_topo1/test_multicast_pim6_static_rp1.py3
-rwxr-xr-xtests/topotests/multicast_pim6_static_rp_topo1/test_multicast_pim6_static_rp2.py3
-rw-r--r--tests/topotests/multicast_pim_sm_topo3/igmp_group_all_detail.json1
-rw-r--r--tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_brief.json51
-rw-r--r--tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_detail.json1
-rw-r--r--tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_brief.json22
-rw-r--r--tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_detail.json1
-rw-r--r--tests/topotests/multicast_pim_sm_topo3/igmp_source_single_if_group_all.json61
-rw-r--r--tests/topotests/multicast_pim_sm_topo3/igmp_source_single_if_single_group.json16
-rwxr-xr-xtests/topotests/multicast_pim_sm_topo3/test_multicast_pim_sm_topo3.py108
-rw-r--r--tests/topotests/munet/__init__.py38
-rw-r--r--tests/topotests/munet/__main__.py236
-rw-r--r--tests/topotests/munet/base.py3068
-rw-r--r--tests/topotests/munet/cleanup.py114
-rw-r--r--tests/topotests/munet/cli.py964
-rw-r--r--tests/topotests/munet/compat.py34
-rw-r--r--tests/topotests/munet/config.py213
-rw-r--r--tests/topotests/munet/kinds.yaml84
-rw-r--r--tests/topotests/munet/linux.py267
-rw-r--r--tests/topotests/munet/logconf-mutest.yaml84
-rw-r--r--tests/topotests/munet/logconf.yaml32
-rw-r--r--tests/topotests/munet/mucmd.py111
-rw-r--r--tests/topotests/munet/mulog.py122
-rw-r--r--tests/topotests/munet/munet-schema.json654
-rw-r--r--tests/topotests/munet/mutest/__main__.py445
-rw-r--r--tests/topotests/munet/mutest/userapi.py1111
-rw-r--r--tests/topotests/munet/mutestshare.py254
-rwxr-xr-xtests/topotests/munet/mutini.py432
-rw-r--r--tests/topotests/munet/native.py2941
-rw-r--r--tests/topotests/munet/parser.py374
-rw-r--r--tests/topotests/munet/testing/__init__.py1
-rw-r--r--tests/topotests/munet/testing/fixtures.py447
-rw-r--r--tests/topotests/munet/testing/hooks.py225
-rw-r--r--tests/topotests/munet/testing/util.py110
-rw-r--r--tests/topotests/ospf6_ecmp_inter_area/r1/ospf6d.conf64
-rw-r--r--tests/topotests/ospf_basic_functionality/ospf_lan.json10
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py520
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_type7_lsa.py22
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_authentication.py44
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_chaos.py32
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_ecmp.py20
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_ecmp_lan.py12
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_lan.py66
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_nssa.py6
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_p2mp.py24
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_routemaps.py40
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_rte_calc.py10
-rw-r--r--tests/topotests/ospf_basic_functionality/test_ospf_single_area.py44
-rw-r--r--tests/topotests/ospf_gr_helper/test_ospf_gr_helper1.py22
-rw-r--r--tests/topotests/ospf_gr_helper/test_ospf_gr_helper2.py28
-rw-r--r--tests/topotests/ospf_gr_helper/test_ospf_gr_helper3.py8
-rw-r--r--tests/topotests/ospf_gr_topo1/rt1/show_ip_ospf_neighbor.json2
-rw-r--r--tests/topotests/ospf_gr_topo1/rt2/show_ip_ospf_neighbor.json4
-rw-r--r--tests/topotests/ospf_gr_topo1/rt3/show_ip_ospf_neighbor.json6
-rw-r--r--tests/topotests/ospf_gr_topo1/rt4/show_ip_ospf_neighbor.json4
-rw-r--r--tests/topotests/ospf_gr_topo1/rt5/show_ip_ospf_neighbor.json2
-rw-r--r--tests/topotests/ospf_gr_topo1/rt6/show_ip_ospf_neighbor.json4
-rw-r--r--tests/topotests/ospf_gr_topo1/rt7/show_ip_ospf_neighbor.json2
-rwxr-xr-xtests/topotests/ospf_metric_propagation/__init__.py0
-rw-r--r--tests/topotests/ospf_metric_propagation/h1/frr.conf10
-rw-r--r--tests/topotests/ospf_metric_propagation/h2/frr.conf10
-rw-r--r--tests/topotests/ospf_metric_propagation/r1/frr.conf96
-rw-r--r--tests/topotests/ospf_metric_propagation/r1/show_ip_route-1.json37
-rw-r--r--tests/topotests/ospf_metric_propagation/r1/show_ip_route-2.json37
-rw-r--r--tests/topotests/ospf_metric_propagation/r1/show_ip_route-3.json37
-rw-r--r--tests/topotests/ospf_metric_propagation/r1/show_ip_route-4.json37
-rw-r--r--tests/topotests/ospf_metric_propagation/r1/show_ip_route-5.json37
-rw-r--r--tests/topotests/ospf_metric_propagation/r1/show_ip_route-6.json37
-rw-r--r--tests/topotests/ospf_metric_propagation/r2/frr.conf81
-rw-r--r--tests/topotests/ospf_metric_propagation/r3/frr.conf79
-rw-r--r--tests/topotests/ospf_metric_propagation/r4/frr.conf78
-rw-r--r--tests/topotests/ospf_metric_propagation/ra/frr.conf27
-rw-r--r--tests/topotests/ospf_metric_propagation/rb/frr.conf27
-rw-r--r--tests/topotests/ospf_metric_propagation/rc/frr.conf21
-rw-r--r--tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py385
-rw-r--r--tests/topotests/ospf_nssa_topo1/__init__.py0
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/ospfd.conf22
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/staticd.conf6
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/step1/show_ip_ospf_route.ref115
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/step10/show_ip_ospf_route.ref115
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/step2/show_ip_ospf_route.ref115
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/step3/show_ip_ospf_route.ref115
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/step4/show_ip_ospf_route.ref103
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/step5/show_ip_ospf_route.ref103
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/step6/show_ip_ospf_route.ref91
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/step7/show_ip_ospf_route.ref103
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/step8/show_ip_ospf_route.ref103
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/step9/show_ip_ospf_route.ref91
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt1/zebra.conf18
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/ospfd.conf35
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/staticd.conf6
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/step1/show_ip_ospf_route.ref127
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/step10/show_ip_ospf_route.ref127
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/step2/show_ip_ospf_route.ref139
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/step3/show_ip_ospf_route.ref127
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/step4/show_ip_ospf_route.ref129
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/step5/show_ip_ospf_route.ref117
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/step6/show_ip_ospf_route.ref103
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/step7/show_ip_ospf_route.ref129
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/step8/show_ip_ospf_route.ref129
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/step9/show_ip_ospf_route.ref127
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt2/zebra.conf24
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/ospfd.conf24
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/staticd.conf8
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/step1/show_ip_ospf_route.ref138
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/step10/show_ip_ospf_route.ref150
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/step2/show_ip_ospf_route.ref138
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/step3/show_ip_ospf_route.ref138
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/step4/show_ip_ospf_route.ref150
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/step5/show_ip_ospf_route.ref138
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/step6/show_ip_ospf_route.ref126
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/step7/show_ip_ospf_route.ref150
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/step8/show_ip_ospf_route.ref150
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/step9/show_ip_ospf_route.ref150
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt3/zebra.conf18
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/ospfd.conf24
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/staticd.conf9
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/step1/show_ip_ospf_route.ref114
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/step10/show_ip_ospf_route.ref126
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/step2/show_ip_ospf_route.ref114
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/step3/show_ip_ospf_route.ref114
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/step4/show_ip_ospf_route.ref126
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/step5/show_ip_ospf_route.ref126
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/step6/show_ip_ospf_route.ref126
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/step7/show_ip_ospf_route.ref126
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/step8/show_ip_ospf_route.ref126
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/step9/show_ip_ospf_route.ref126
-rw-r--r--tests/topotests/ospf_nssa_topo1/rt4/zebra.conf18
-rw-r--r--tests/topotests/ospf_nssa_topo1/test_ospf_nssa_topo1.py416
-rw-r--r--tests/topotests/ospf_suppress_fa/test_ospf_suppress_fa.py4
-rw-r--r--tests/topotests/ospf_te_topo1/reference/ted_step1.json18
-rw-r--r--tests/topotests/ospf_te_topo1/reference/ted_step2.json14
-rw-r--r--tests/topotests/ospf_te_topo1/reference/ted_step3.json12
-rw-r--r--tests/topotests/ospf_te_topo1/reference/ted_step4.json12
-rw-r--r--tests/topotests/ospf_te_topo1/reference/ted_step5.json16
-rw-r--r--tests/topotests/ospf_te_topo1/reference/ted_step6.json16
-rw-r--r--tests/topotests/ospf_te_topo1/reference/ted_step7.json12
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py236
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py80
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp.py8
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp_lan.py8
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_nssa.py2
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_nssa2.py8
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_routemaps.py12
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_rte_calc.py6
-rw-r--r--tests/topotests/ospfv3_basic_functionality/test_ospfv3_single_area.py97
-rw-r--r--tests/topotests/pim_acl/r1/ospf_neighbor.json50
-rw-r--r--tests/topotests/pim_igmp_vrf/r1/ospf_blue_neighbor.json4
-rw-r--r--tests/topotests/pim_igmp_vrf/r1/ospf_red_neighbor.json4
-rw-r--r--tests/topotests/pytest.ini4
-rw-r--r--tests/topotests/rip_allow_ecmp/__init__.py0
-rw-r--r--tests/topotests/rip_allow_ecmp/r1/frr.conf9
-rw-r--r--tests/topotests/rip_allow_ecmp/r2/frr.conf13
-rw-r--r--tests/topotests/rip_allow_ecmp/r3/frr.conf13
-rw-r--r--tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py132
-rwxr-xr-xtests/topotests/rip_bfd_topo1/__init__.py0
-rw-r--r--tests/topotests/rip_bfd_topo1/r1/bfdd.conf6
-rw-r--r--tests/topotests/rip_bfd_topo1/r1/ripd.conf17
-rw-r--r--tests/topotests/rip_bfd_topo1/r1/zebra.conf11
-rw-r--r--tests/topotests/rip_bfd_topo1/r2/bfdd.conf6
-rw-r--r--tests/topotests/rip_bfd_topo1/r2/ripd.conf11
-rw-r--r--tests/topotests/rip_bfd_topo1/r2/staticd.conf1
-rw-r--r--tests/topotests/rip_bfd_topo1/r2/zebra.conf8
-rw-r--r--tests/topotests/rip_bfd_topo1/r3/bfdd.conf6
-rw-r--r--tests/topotests/rip_bfd_topo1/r3/ripd.conf11
-rw-r--r--tests/topotests/rip_bfd_topo1/r3/staticd.conf1
-rw-r--r--tests/topotests/rip_bfd_topo1/r3/zebra.conf7
-rw-r--r--tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.dot58
-rw-r--r--tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.pngbin0 -> 27400 bytes
-rw-r--r--tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.py252
-rw-r--r--tests/topotests/rip_passive_interface/__init__.py0
-rw-r--r--tests/topotests/rip_passive_interface/r1/frr.conf9
-rw-r--r--tests/topotests/rip_passive_interface/r2/frr.conf13
-rw-r--r--tests/topotests/rip_passive_interface/r3/frr.conf13
-rw-r--r--tests/topotests/rip_passive_interface/test_rip_passive_interface.py102
-rw-r--r--tests/topotests/rip_topo1/r1/rip_status.ref2
-rw-r--r--tests/topotests/rip_topo1/r2/rip_status.ref4
-rw-r--r--tests/topotests/rip_topo1/r3/rip_status.ref2
-rw-r--r--tests/topotests/zebra_rib/test_zebra_rib.py3
452 files changed, 53346 insertions, 3121 deletions
diff --git a/tests/bgpd/test_mp_attr.c b/tests/bgpd/test_mp_attr.c
index 54596dbdfb..ae7903e0cc 100644
--- a/tests/bgpd/test_mp_attr.c
+++ b/tests/bgpd/test_mp_attr.c
@@ -1042,9 +1042,9 @@ static void parse_test(struct peer *peer, struct test_segment *t, int type)
if (!parse_ret) {
if (type == BGP_ATTR_MP_REACH_NLRI)
- nlri_ret = bgp_nlri_parse(peer, &attr, &nlri, 0);
+ nlri_ret = bgp_nlri_parse(peer, &attr, &nlri, false);
else if (type == BGP_ATTR_MP_UNREACH_NLRI)
- nlri_ret = bgp_nlri_parse(peer, &attr, &nlri, 1);
+ nlri_ret = bgp_nlri_parse(peer, &attr, &nlri, true);
}
handle_result(peer, t, parse_ret, nlri_ret);
}
diff --git a/tests/isisd/test_common.c b/tests/isisd/test_common.c
index d0288f600d..e47456965e 100644
--- a/tests/isisd/test_common.c
+++ b/tests/isisd/test_common.c
@@ -98,7 +98,7 @@ static void lsp_add_ip_reach(struct isis_lsp *lsp,
{
struct prefix prefix;
struct sr_prefix_cfg pcfg = {};
- struct sr_prefix_cfg *pcfg_p = NULL;
+ struct sr_prefix_cfg *pcfg_p[SR_ALGORITHM_COUNT] = {NULL};
if (str2prefix(prefix_str, &prefix) != 1) {
zlog_debug("%s: invalid network: %s", __func__, prefix_str);
@@ -106,7 +106,7 @@ static void lsp_add_ip_reach(struct isis_lsp *lsp,
}
if (CHECK_FLAG(tnode->flags, F_ISIS_TEST_NODE_SR)) {
- pcfg_p = &pcfg;
+ pcfg_p[SR_ALGORITHM_SPF] = &pcfg;
pcfg.sid = *next_sid_index;
*next_sid_index = *next_sid_index + 1;
@@ -163,31 +163,32 @@ static void lsp_add_reach(struct isis_lsp *lsp,
static void lsp_add_router_capability(struct isis_lsp *lsp,
const struct isis_test_node *tnode)
{
- struct isis_router_cap cap = {};
+ struct isis_router_cap *cap;
if (!tnode->router_id)
return;
- if (inet_pton(AF_INET, tnode->router_id, &cap.router_id) != 1) {
+ cap = isis_tlvs_init_router_capability(lsp->tlvs);
+
+ if (inet_pton(AF_INET, tnode->router_id, &cap->router_id) != 1) {
zlog_debug("%s: invalid router-id: %s", __func__,
tnode->router_id);
return;
}
if (CHECK_FLAG(tnode->flags, F_ISIS_TEST_NODE_SR)) {
- cap.srgb.flags =
+ cap->srgb.flags =
ISIS_SUBTLV_SRGB_FLAG_I | ISIS_SUBTLV_SRGB_FLAG_V;
- cap.srgb.lower_bound = tnode->srgb.lower_bound
- ? tnode->srgb.lower_bound
- : SRGB_DFTL_LOWER_BOUND;
- cap.srgb.range_size = tnode->srgb.range_size
- ? tnode->srgb.range_size
- : SRGB_DFTL_RANGE_SIZE;
- cap.algo[0] = SR_ALGORITHM_SPF;
- cap.algo[1] = SR_ALGORITHM_UNSET;
+ cap->srgb.lower_bound = tnode->srgb.lower_bound
+ ? tnode->srgb.lower_bound
+ : SRGB_DFTL_LOWER_BOUND;
+ cap->srgb.range_size = tnode->srgb.range_size
+ ? tnode->srgb.range_size
+ : SRGB_DFTL_RANGE_SIZE;
+ cap->algo[0] = SR_ALGORITHM_SPF;
+ cap->algo[1] = SR_ALGORITHM_UNSET;
}
- isis_tlvs_set_router_capability(lsp->tlvs, &cap);
}
static void lsp_add_mt_router_info(struct isis_lsp *lsp,
diff --git a/tests/isisd/test_isis_spf.c b/tests/isisd/test_isis_spf.c
index 85ddfea5b5..6eb180b501 100644
--- a/tests/isisd/test_isis_spf.c
+++ b/tests/isisd/test_isis_spf.c
@@ -49,12 +49,13 @@ static void test_run_spf(struct vty *vty, const struct isis_topology *topology,
/* Run SPF. */
spf_type = reverse ? SPF_TYPE_REVERSE : SPF_TYPE_FORWARD;
spftree = isis_spftree_new(area, lspdb, root->sysid, level, tree,
- spf_type, F_SPFTREE_NO_ADJACENCIES);
+ spf_type, F_SPFTREE_NO_ADJACENCIES,
+ SR_ALGORITHM_SPF);
isis_run_spf(spftree);
/* Print the SPT and the corresponding routing table. */
isis_print_spftree(vty, spftree);
- isis_print_routes(vty, spftree, false, false);
+ isis_print_routes(vty, spftree, NULL, false, false);
/* Cleanup SPF tree. */
isis_spftree_del(spftree);
@@ -71,8 +72,9 @@ static void test_run_lfa(struct vty *vty, const struct isis_topology *topology,
/* Run forward SPF in the root node. */
flags = F_SPFTREE_NO_ADJACENCIES;
- spftree_self = isis_spftree_new(area, lspdb, root->sysid, level, tree,
- SPF_TYPE_FORWARD, flags);
+ spftree_self =
+ isis_spftree_new(area, lspdb, root->sysid, level, tree,
+ SPF_TYPE_FORWARD, flags, SR_ALGORITHM_SPF);
isis_run_spf(spftree_self);
/* Run forward SPF on all adjacent routers. */
@@ -84,9 +86,9 @@ static void test_run_lfa(struct vty *vty, const struct isis_topology *topology,
/* Print the SPT and the corresponding main/backup routing tables. */
isis_print_spftree(vty, spftree_self);
vty_out(vty, "Main:\n");
- isis_print_routes(vty, spftree_self, false, false);
+ isis_print_routes(vty, spftree_self, NULL, false, false);
vty_out(vty, "Backup:\n");
- isis_print_routes(vty, spftree_self, false, true);
+ isis_print_routes(vty, spftree_self, NULL, false, true);
/* Cleanup everything. */
isis_spftree_del(spftree_self);
@@ -107,8 +109,9 @@ static void test_run_rlfa(struct vty *vty, const struct isis_topology *topology,
/* Run forward SPF in the root node. */
flags = F_SPFTREE_NO_ADJACENCIES;
- spftree_self = isis_spftree_new(area, lspdb, root->sysid, level, tree,
- SPF_TYPE_FORWARD, flags);
+ spftree_self =
+ isis_spftree_new(area, lspdb, root->sysid, level, tree,
+ SPF_TYPE_FORWARD, flags, SR_ALGORITHM_SPF);
isis_run_spf(spftree_self);
/* Run reverse SPF in the root node. */
@@ -162,9 +165,9 @@ static void test_run_rlfa(struct vty *vty, const struct isis_topology *topology,
/* Print the SPT and the corresponding main/backup routing tables. */
isis_print_spftree(vty, spftree_self);
vty_out(vty, "Main:\n");
- isis_print_routes(vty, spftree_self, false, false);
+ isis_print_routes(vty, spftree_self, NULL, false, false);
vty_out(vty, "Backup:\n");
- isis_print_routes(vty, spftree_self, false, true);
+ isis_print_routes(vty, spftree_self, NULL, false, true);
/* Cleanup everything. */
isis_spftree_del(spftree_self);
@@ -187,8 +190,9 @@ static void test_run_ti_lfa(struct vty *vty,
/* Run forward SPF in the root node. */
flags = F_SPFTREE_NO_ADJACENCIES;
- spftree_self = isis_spftree_new(area, lspdb, root->sysid, level, tree,
- SPF_TYPE_FORWARD, flags);
+ spftree_self =
+ isis_spftree_new(area, lspdb, root->sysid, level, tree,
+ SPF_TYPE_FORWARD, flags, SR_ALGORITHM_SPF);
isis_run_spf(spftree_self);
/* Run reverse SPF in the root node. */
@@ -224,7 +228,7 @@ static void test_run_ti_lfa(struct vty *vty,
* Print the post-convergence SPT and the corresponding routing table.
*/
isis_print_spftree(vty, spftree_pc);
- isis_print_routes(vty, spftree_self, false, true);
+ isis_print_routes(vty, spftree_self, NULL, false, true);
/* Cleanup everything. */
isis_spftree_del(spftree_self);
diff --git a/tests/lib/cxxcompat.c b/tests/lib/cxxcompat.c
index 8f54856186..4ad41fca42 100644
--- a/tests/lib/cxxcompat.c
+++ b/tests/lib/cxxcompat.c
@@ -25,7 +25,6 @@
#include "lib/frr_pthread.h"
#include "lib/frratomic.h"
#include "lib/frrstr.h"
-#include "lib/getopt.h"
#include "lib/graph.h"
#include "lib/hash.h"
#include "lib/hook.h"
diff --git a/tests/lib/subdir.am b/tests/lib/subdir.am
index 1bc092a49e..e950d0120d 100644
--- a/tests/lib/subdir.am
+++ b/tests/lib/subdir.am
@@ -15,7 +15,7 @@ tests_lib_test_frrscript_CFLAGS = $(TESTS_CFLAGS)
tests_lib_test_frrscript_CPPFLAGS = $(TESTS_CPPFLAGS)
tests_lib_test_frrscript_LDADD = $(ALL_TESTS_LDADD)
tests_lib_test_frrscript_SOURCES = tests/lib/test_frrscript.c
-EXTRA_DIST += tests/lib/test_frrscript.py
+EXTRA_DIST += tests/lib/test_frrscript.py tests/lib/script1.lua
##############################################################################
diff --git a/tests/lib/test_typelist.h b/tests/lib/test_typelist.h
index 91528139b5..80c4005437 100644
--- a/tests/lib/test_typelist.h
+++ b/tests/lib/test_typelist.h
@@ -171,6 +171,11 @@ static void concat(test_, TYPE)(void)
ts_hash("init", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119");
+#if !IS_ATOMIC(REALTYPE)
+ assert(!list_member(&head, &itm[0]));
+ assert(!list_member(&head, &itm[1]));
+#endif
+
#if IS_SORTED(REALTYPE)
prng = prng_new(0);
k = 0;
diff --git a/tests/topotests/all_protocol_startup/r1/ospf6d.conf-pre-v4 b/tests/topotests/all_protocol_startup/r1/ospf6d.conf-pre-v4
index 6d870f355f..9ce2f2e825 100644
--- a/tests/topotests/all_protocol_startup/r1/ospf6d.conf-pre-v4
+++ b/tests/topotests/all_protocol_startup/r1/ospf6d.conf-pre-v4
@@ -1,9 +1,9 @@
log file ospf6d.log
!
-debug ospf6 lsa unknown
-debug ospf6 zebra
-debug ospf6 interface
-debug ospf6 neighbor
+!debug ospf6 lsa unknown
+!debug ospf6 zebra
+!debug ospf6 interface
+!debug ospf6 neighbor
!
interface r1-eth4
!
diff --git a/tests/topotests/all_protocol_startup/test_all_protocol_startup.py b/tests/topotests/all_protocol_startup/test_all_protocol_startup.py
index f7c3a4c19d..92bb99c8f2 100644
--- a/tests/topotests/all_protocol_startup/test_all_protocol_startup.py
+++ b/tests/topotests/all_protocol_startup/test_all_protocol_startup.py
@@ -288,6 +288,17 @@ def test_converge_protocols():
thisDir = os.path.dirname(os.path.realpath(__file__))
+ # We need loopback to have a link local so it always is the
+ # "selected" router for fe80::/64 when we static compare below.
+ print("Adding link-local to loopback for stable results")
+ cmd = (
+ "mac=`cat /sys/class/net/lo/address`; echo lo: $mac;"
+ " [ -z \"$mac\" ] && continue; IFS=':'; set $mac; unset IFS;"
+ " ip address add dev lo scope link"
+ " fe80::$(printf %02x $((0x$1 ^ 2)))$2:${3}ff:fe$4:$5$6/64"
+ )
+ net["r1"].cmd_raises(cmd)
+
print("\n\n** Waiting for protocols convergence")
print("******************************************\n")
diff --git a/tests/topotests/bfd_topo3/r5/bfdd.conf b/tests/topotests/bfd_topo3/r5/bfdd.conf
index 6d4483acc4..ec62d8d275 100644
--- a/tests/topotests/bfd_topo3/r5/bfdd.conf
+++ b/tests/topotests/bfd_topo3/r5/bfdd.conf
@@ -1,6 +1,6 @@
-debug bfd network
-debug bfd peer
-debug bfd zebra
+!debug bfd network
+!debug bfd peer
+!debug bfd zebra
!
bfd
profile slow-tx
diff --git a/tests/topotests/bfd_topo3/r6/bfdd.conf b/tests/topotests/bfd_topo3/r6/bfdd.conf
index 6d4483acc4..ec62d8d275 100644
--- a/tests/topotests/bfd_topo3/r6/bfdd.conf
+++ b/tests/topotests/bfd_topo3/r6/bfdd.conf
@@ -1,6 +1,6 @@
-debug bfd network
-debug bfd peer
-debug bfd zebra
+!debug bfd network
+!debug bfd peer
+!debug bfd zebra
!
bfd
profile slow-tx
diff --git a/tests/topotests/bgp_accept_own/ce1/bgpd.conf b/tests/topotests/bgp_accept_own/ce1/bgpd.conf
index fa53a42919..44f95b9bb3 100644
--- a/tests/topotests/bgp_accept_own/ce1/bgpd.conf
+++ b/tests/topotests/bgp_accept_own/ce1/bgpd.conf
@@ -1,5 +1,5 @@
!
-debug bgp updates
+!debug bgp updates
!
router bgp 65010
no bgp ebgp-requires-policy
diff --git a/tests/topotests/bgp_accept_own/ce2/bgpd.conf b/tests/topotests/bgp_accept_own/ce2/bgpd.conf
index cdf8898c90..d60fdcf7cb 100644
--- a/tests/topotests/bgp_accept_own/ce2/bgpd.conf
+++ b/tests/topotests/bgp_accept_own/ce2/bgpd.conf
@@ -1,5 +1,5 @@
!
-debug bgp updates
+!debug bgp updates
!
router bgp 65020
no bgp ebgp-requires-policy
diff --git a/tests/topotests/bgp_accept_own/pe1/bgpd.conf b/tests/topotests/bgp_accept_own/pe1/bgpd.conf
index 109e0eadbb..15466b4259 100644
--- a/tests/topotests/bgp_accept_own/pe1/bgpd.conf
+++ b/tests/topotests/bgp_accept_own/pe1/bgpd.conf
@@ -1,8 +1,8 @@
!
-debug bgp updates
-debug bgp vpn leak-from-vrf
-debug bgp vpn leak-to-vrf
-debug bgp nht
+!debug bgp updates
+!debug bgp vpn leak-from-vrf
+!debug bgp vpn leak-to-vrf
+!debug bgp nht
!
router bgp 65001
bgp router-id 10.10.10.10
diff --git a/tests/topotests/bgp_accept_own/rr1/bgpd.conf b/tests/topotests/bgp_accept_own/rr1/bgpd.conf
index 4f0a6ab0f1..ad0ee3e471 100644
--- a/tests/topotests/bgp_accept_own/rr1/bgpd.conf
+++ b/tests/topotests/bgp_accept_own/rr1/bgpd.conf
@@ -1,5 +1,5 @@
!
-debug bgp updates
+!debug bgp updates
!
router bgp 65001
bgp router-id 10.10.10.101
diff --git a/tests/topotests/bgp_bfd_down_cease_notification/r1/bgpd.conf b/tests/topotests/bgp_bfd_down_cease_notification/r1/bgpd.conf
index 2071c256da..e855f75c20 100644
--- a/tests/topotests/bgp_bfd_down_cease_notification/r1/bgpd.conf
+++ b/tests/topotests/bgp_bfd_down_cease_notification/r1/bgpd.conf
@@ -2,7 +2,9 @@ router bgp 65001
no bgp ebgp-requires-policy
neighbor 192.168.255.2 remote-as external
neighbor 192.168.255.2 timers 3 10
+ neighbor 192.168.255.2 timers connect 1
neighbor 192.168.255.2 bfd
+ neighbor 192.168.255.2 passive
address-family ipv4
redistribute connected
exit-address-family
diff --git a/tests/topotests/bgp_bfd_down_cease_notification/r2/bgpd.conf b/tests/topotests/bgp_bfd_down_cease_notification/r2/bgpd.conf
index 3279804e6e..faf2c6b39b 100644
--- a/tests/topotests/bgp_bfd_down_cease_notification/r2/bgpd.conf
+++ b/tests/topotests/bgp_bfd_down_cease_notification/r2/bgpd.conf
@@ -2,6 +2,7 @@ router bgp 65002
no bgp ebgp-requires-policy
neighbor 192.168.255.1 remote-as external
neighbor 192.168.255.1 timers 3 10
+ neighbor 192.168.255.1 timers connect 1
neighbor 192.168.255.1 bfd
address-family ipv4
redistribute connected
diff --git a/tests/topotests/bgp_bfd_down_cease_notification/test_bgp_bfd_down_cease_notification.py b/tests/topotests/bgp_bfd_down_cease_notification/test_bgp_bfd_down_cease_notification.py
index 0bc0306d7d..00142981c5 100644
--- a/tests/topotests/bgp_bfd_down_cease_notification/test_bgp_bfd_down_cease_notification.py
+++ b/tests/topotests/bgp_bfd_down_cease_notification/test_bgp_bfd_down_cease_notification.py
@@ -88,13 +88,14 @@ def test_bgp_bfd_down_notification():
expected = {
"192.168.255.1": {
"lastNotificationReason": "Cease/BFD Down",
+ "lastNotificationHardReset": True,
}
}
return topotest.json_cmp(output, expected)
step("Initial BGP converge")
test_func = functools.partial(_bgp_converge)
- _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
assert result is None, "Failed to see BGP convergence on R2"
step("Kill bfdd on R2")
@@ -102,7 +103,7 @@ def test_bgp_bfd_down_notification():
step("Check if we received Cease/BFD Down notification message")
test_func = functools.partial(_bgp_bfd_down_notification)
- _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
assert result is None, "Failed to see BGP Cease/BFD Down notification message on R2"
diff --git a/tests/topotests/bgp_comm_list_match/r2/bgpd.conf b/tests/topotests/bgp_comm_list_match/r2/bgpd.conf
index 35ad2d32e6..98a9780688 100644
--- a/tests/topotests/bgp_comm_list_match/r2/bgpd.conf
+++ b/tests/topotests/bgp_comm_list_match/r2/bgpd.conf
@@ -1,5 +1,5 @@
!
-debug bgp updates
+!debug bgp updates
!
router bgp 65002
no bgp ebgp-requires-policy
diff --git a/tests/topotests/bgp_confed1/r1/bgpd.conf b/tests/topotests/bgp_confed1/r1/bgpd.conf
index 8413ef7fc3..107d2ad8d2 100644
--- a/tests/topotests/bgp_confed1/r1/bgpd.conf
+++ b/tests/topotests/bgp_confed1/r1/bgpd.conf
@@ -1,7 +1,7 @@
-debug bgp neighbor-events
-debug bgp nht
-debug bgp updates in
-debug bgp updates out
+!debug bgp neighbor-events
+!debug bgp nht
+!debug bgp updates in
+!debug bgp updates out
!
router bgp 100
no bgp ebgp-requires-policy
diff --git a/tests/topotests/bgp_confed1/r2/bgpd.conf b/tests/topotests/bgp_confed1/r2/bgpd.conf
index 9f6a9852de..fe13dfe729 100644
--- a/tests/topotests/bgp_confed1/r2/bgpd.conf
+++ b/tests/topotests/bgp_confed1/r2/bgpd.conf
@@ -1,7 +1,7 @@
-debug bgp neighbor-events
-debug bgp nht
-debug bgp updates in
-debug bgp updates out
+!debug bgp neighbor-events
+!debug bgp nht
+!debug bgp updates in
+!debug bgp updates out
!
router bgp 200
no bgp ebgp-requires-policy
diff --git a/tests/topotests/bgp_confed1/r3/bgpd.conf b/tests/topotests/bgp_confed1/r3/bgpd.conf
index 3a018a42b3..74d5fd6e29 100644
--- a/tests/topotests/bgp_confed1/r3/bgpd.conf
+++ b/tests/topotests/bgp_confed1/r3/bgpd.conf
@@ -1,7 +1,7 @@
-debug bgp neighbor-events
-debug bgp nht
-debug bgp updates in
-debug bgp updates out
+!debug bgp neighbor-events
+!debug bgp nht
+!debug bgp updates in
+!debug bgp updates out
!
router bgp 300
no bgp ebgp-requires-policy
diff --git a/tests/topotests/bgp_confed1/r4/bgpd.conf b/tests/topotests/bgp_confed1/r4/bgpd.conf
index 134f221543..89a85e5a34 100644
--- a/tests/topotests/bgp_confed1/r4/bgpd.conf
+++ b/tests/topotests/bgp_confed1/r4/bgpd.conf
@@ -1,7 +1,7 @@
-debug bgp neighbor-events
-debug bgp nht
-debug bgp updates in
-debug bgp updates out
+!debug bgp neighbor-events
+!debug bgp nht
+!debug bgp updates in
+!debug bgp updates out
!
router bgp 400
no bgp ebgp-requires-policy
diff --git a/tests/topotests/bgp_dont_capability_negotiate/r1/bgpd.conf b/tests/topotests/bgp_dont_capability_negotiate/r1/bgpd.conf
index e2ff1df965..2f76d59d4a 100644
--- a/tests/topotests/bgp_dont_capability_negotiate/r1/bgpd.conf
+++ b/tests/topotests/bgp_dont_capability_negotiate/r1/bgpd.conf
@@ -1,5 +1,5 @@
!
-debug bgp neighbor
+!debug bgp neighbor
!
router bgp 65001
no bgp ebgp-requires-policy
diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py b/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py
index f8af210ed7..65c0c3532a 100755
--- a/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py
+++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py
@@ -83,6 +83,7 @@ def build_topo(tgen):
switch.add_link(tgen.gears["PE2"])
switch.add_link(tgen.gears["host2"])
+
def setup_pe_router(tgen, pe_name, tunnel_local_ip, svi_ip, intf):
pe = tgen.gears[pe_name]
@@ -100,7 +101,9 @@ def setup_pe_router(tgen, pe_name, tunnel_local_ip, svi_ip, intf):
# setup single vxlan device
pe.run(
- "ip link add dev vxlan0 type vxlan dstport 4789 local {0} nolearning external".format(tunnel_local_ip)
+ "ip link add dev vxlan0 type vxlan dstport 4789 local {0} nolearning external".format(
+ tunnel_local_ip
+ )
)
pe.run("ip link set dev vxlan0 master bridge")
pe.run("bridge link set dev vxlan0 vlan_tunnel on")
@@ -136,10 +139,12 @@ def setup_pe_router(tgen, pe_name, tunnel_local_ip, svi_ip, intf):
pe.run("bridge vlan add dev vxlan0 vid 300")
pe.run("bridge vlan add dev vxlan0 vid 300 tunnel_info id 300")
+
def setup_p_router(tgen, p_name):
p1 = tgen.gears[p_name]
p1.run("sysctl -w net.ipv4.ip_forward=1")
+
def setup_module(mod):
"Sets up the pytest environment"
@@ -180,7 +185,7 @@ def teardown_module(mod):
"Teardown the pytest environment"
tgen = get_topogen()
- #tgen.mininet_cli()
+ # tgen.mininet_cli()
# This function tears down the whole topology.
tgen.stop_topology()
@@ -204,17 +209,21 @@ def check_vni_macs_present(tgen, router, vni, maclist):
)
return None
+
def check_flood_entry_present(pe, vni, vtep):
if not topotest.iproute2_is_fdb_get_capable():
return None
- output = pe.run("bridge fdb get 00:00:00:00:00:00 dev vxlan0 vni {} self".format(vni))
+ output = pe.run(
+ "bridge fdb get 00:00:00:00:00:00 dev vxlan0 vni {} self".format(vni)
+ )
if str(vtep) not in output:
return output
return None
+
def test_pe1_converge_evpn():
"Wait for protocol convergence"
@@ -231,6 +240,15 @@ def test_pe1_converge_evpn():
_, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
assertmsg = '"{}" JSON output mismatches'.format(pe1.name)
+ # Let's ensure that the hosts have actually tried talking to
+ # each other. Otherwise under certain startup conditions
+ # they may not actually do any l2 arp'ing and as such
+ # the bridges won't know about the hosts on their networks
+ host1 = tgen.gears["host1"]
+ host1.run("ping -c 1 10.10.1.56")
+ host2 = tgen.gears["host2"]
+ host2.run("ping -c 1 10.10.1.55")
+
test_func = partial(
check_vni_macs_present,
tgen,
@@ -249,11 +267,12 @@ def test_pe1_converge_evpn():
assertmsg = '"{}" Flood FDB Entry for VTEP {} not found'.format(pe1.name, vtep)
assert result is None, assertmsg
+
def test_pe2_converge_evpn():
"Wait for protocol convergence"
tgen = get_topogen()
-#Don't run this test if we have any failure.
+ # Don't run this test if we have any failure.
if tgen.routers_have_failure():
pytest.skip(tgen.errors)
@@ -284,6 +303,7 @@ def test_pe2_converge_evpn():
assertmsg = '"{}" Flood FDB Entry for VTEP {} not found'.format(pe2.name, vtep)
assert result is None, assertmsg
+
def mac_learn_test(host, local):
"check the host MAC gets learned by the VNI"
@@ -389,11 +409,11 @@ def ip_learn_test(tgen, host, local, remote, ip_addr):
if "HWaddr" in line_items[0]:
mac = line_items[1]
break
- #print(host_output)
+ # print(host_output)
# check we have a local association between the MAC and IP
local_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac))
- #print(local_output)
+ # print(local_output)
local_output_json = json.loads(local_output)
mac_type = local_output_json[mac]["type"]
assertmsg = "Failed to learn local IP address on host {}".format(host.name)
@@ -417,7 +437,7 @@ def ip_learn_test(tgen, host, local, remote, ip_addr):
remote_output = remote.vtysh_cmd(
"show evpn mac vni 101 mac {} json".format(mac)
)
- #print(remote_output)
+ # print(remote_output)
remote_output_json = json.loads(remote_output)
type = remote_output_json[mac]["type"]
if not remote_output_json[mac]["neighbors"] == "none":
@@ -431,12 +451,12 @@ def ip_learn_test(tgen, host, local, remote, ip_addr):
count += 1
sleep(1)
- #print("tries: {}".format(count))
+ # print("tries: {}".format(count))
assertmsg = "{} remote learned mac no address: {} ".format(host.name, mac)
# some debug for this failure
if not converged == True:
log_output = remote.run("cat zebra.log")
- #print(log_output)
+ # print(log_output)
assert converged == True, assertmsg
if remote_output_json[mac]["neighbors"]["active"]:
@@ -463,8 +483,8 @@ def test_ip_pe1_learn():
host1 = tgen.gears["host1"]
pe1 = tgen.gears["PE1"]
pe2 = tgen.gears["PE2"]
- pe2.vtysh_cmd("debug zebra vxlan")
- pe2.vtysh_cmd("debug zebra kernel")
+ # pe2.vtysh_cmd("debug zebra vxlan")
+ # pe2.vtysh_cmd("debug zebra kernel")
# lets populate that arp cache
host1.run("ping -c1 10.10.1.1")
ip_learn_test(tgen, host1, pe1, pe2, "10.10.1.55")
@@ -482,13 +502,14 @@ def test_ip_pe2_learn():
host2 = tgen.gears["host2"]
pe1 = tgen.gears["PE1"]
pe2 = tgen.gears["PE2"]
- pe1.vtysh_cmd("debug zebra vxlan")
- pe1.vtysh_cmd("debug zebra kernel")
+ # pe1.vtysh_cmd("debug zebra vxlan")
+ # pe1.vtysh_cmd("debug zebra kernel")
# lets populate that arp cache
host2.run("ping -c1 10.10.1.3")
ip_learn_test(tgen, host2, pe2, pe1, "10.10.1.56")
# tgen.mininet_cli()
+
def show_dvni_route(pe, vni, prefix, vrf):
output = pe.vtysh_cmd("show ip route vrf {} {}".format(vrf, prefix))
@@ -502,6 +523,7 @@ def show_dvni_route(pe, vni, prefix, vrf):
return None
+
def test_dvni():
"test Downstream VNI works as expected importing into PE1"
@@ -517,7 +539,7 @@ def test_dvni():
_, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
assertmsg = '"{}" DVNI route {} not found'.format(pe1.name, prefix)
assert result is None, assertmsg
- #tgen.mininet_cli()
+ # tgen.mininet_cli()
def test_memory_leak():
diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py b/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py
index 48b79ab5ee..2884043012 100755
--- a/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py
+++ b/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py
@@ -164,6 +164,15 @@ def test_pe1_converge_evpn():
_, result = topotest.run_and_expect(test_func, None, count=45, wait=1)
assertmsg = '"{}" JSON output mismatches'.format(pe1.name)
+ # Let's ensure that the hosts have actually tried talking to
+ # each other. Otherwise under certain startup conditions
+ # they may not actually do any l2 arp'ing and as such
+ # the bridges won't know about the hosts on their networks
+ host1 = tgen.gears["host1"]
+ host1.run("ping -c 1 10.10.1.56")
+ host2 = tgen.gears["host2"]
+ host2.run("ping -c 1 10.10.1.55")
+
test_func = partial(
check_vni_macs_present,
tgen,
@@ -171,6 +180,7 @@ def test_pe1_converge_evpn():
101,
(("host1", "host1-eth0"), ("host2", "host2-eth0")),
)
+
_, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
if result:
logger.warning("%s", result)
@@ -385,8 +395,8 @@ def test_ip_pe1_learn():
host1 = tgen.gears["host1"]
pe1 = tgen.gears["PE1"]
pe2 = tgen.gears["PE2"]
- pe2.vtysh_cmd("debug zebra vxlan")
- pe2.vtysh_cmd("debug zebra kernel")
+ # pe2.vtysh_cmd("debug zebra vxlan")
+ # pe2.vtysh_cmd("debug zebra kernel")
# lets populate that arp cache
host1.run("ping -c1 10.10.1.1")
ip_learn_test(tgen, host1, pe1, pe2, "10.10.1.55")
@@ -404,8 +414,8 @@ def test_ip_pe2_learn():
host2 = tgen.gears["host2"]
pe1 = tgen.gears["PE1"]
pe2 = tgen.gears["PE2"]
- pe1.vtysh_cmd("debug zebra vxlan")
- pe1.vtysh_cmd("debug zebra kernel")
+ # pe1.vtysh_cmd("debug zebra vxlan")
+ # pe1.vtysh_cmd("debug zebra kernel")
# lets populate that arp cache
host2.run("ping -c1 10.10.1.3")
ip_learn_test(tgen, host2, pe2, pe1, "10.10.1.56")
diff --git a/tests/topotests/bgp_features/r1/ospf_neighbor.json b/tests/topotests/bgp_features/r1/ospf_neighbor.json
index 3b5f46d934..caf700d82a 100644
--- a/tests/topotests/bgp_features/r1/ospf_neighbor.json
+++ b/tests/topotests/bgp_features/r1/ospf_neighbor.json
@@ -2,13 +2,13 @@
"neighbors":{
"192.168.0.2":[
{
- "priority":5,
+ "nbrPriority":5,
"converged":"Full"
}
],
"192.168.0.3":[
{
- "priority":5,
+ "nbrPriority":5,
"converged":"Full"
}
]
diff --git a/tests/topotests/bgp_features/r2/ospf_neighbor.json b/tests/topotests/bgp_features/r2/ospf_neighbor.json
index 47bb57cd00..3a168ba335 100644
--- a/tests/topotests/bgp_features/r2/ospf_neighbor.json
+++ b/tests/topotests/bgp_features/r2/ospf_neighbor.json
@@ -2,13 +2,13 @@
"neighbors":{
"192.168.0.1":[
{
- "priority":10,
+ "nbrPriority":10,
"converged":"Full"
}
],
"192.168.0.3":[
{
- "priority":5,
+ "nbrPriority":5,
"converged":"Full"
}
]
diff --git a/tests/topotests/bgp_features/r3/ospf_neighbor.json b/tests/topotests/bgp_features/r3/ospf_neighbor.json
index b84974ccca..9f8c05949f 100644
--- a/tests/topotests/bgp_features/r3/ospf_neighbor.json
+++ b/tests/topotests/bgp_features/r3/ospf_neighbor.json
@@ -2,13 +2,13 @@
"neighbors":{
"192.168.0.1":[
{
- "priority":10,
+ "nbrPriority":10,
"converged":"Full"
}
],
"192.168.0.2":[
{
- "priority":10,
+ "nbrPriority":10,
"converged":"Full"
}
]
diff --git a/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-3.py b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-3.py
index 6388295c95..1a8f8302ff 100644
--- a/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-3.py
+++ b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-3.py
@@ -1159,7 +1159,7 @@ def test_BGP_GR_TC_31_2_p1(request):
reset_config_on_routers(tgen)
logger.info(
- "[Phase 1] : Test Setup " "[Disable Mode]R1-----R2[Restart Mode] initialized "
+ "[Phase 1] : Test Setup " "[Disable Mode]R1-----R2[Helper Mode] initialized "
)
# Configure graceful-restart
@@ -1251,7 +1251,7 @@ def test_BGP_GR_TC_31_2_p1(request):
tc_name, result
)
- logger.info("[Phase 2] : R2 Goes from Disable to Restart Mode ")
+ logger.info("[Phase 2] : R1 Goes from Disable to Restart Mode ")
# Configure graceful-restart
input_dict = {
@@ -1356,31 +1356,7 @@ def test_BGP_GR_TC_31_2_p1(request):
},
}
- # here the verify_graceful_restart fro the neighbor would be
- # "NotReceived" as the latest GR config is not yet applied.
- for addr_type in ADDR_TYPES:
- result = verify_graceful_restart(
- tgen, topo, addr_type, input_dict, dut="r1", peer="r2"
- )
- assert result is True, "Testcase {} : Failed \n Error {}".format(
- tc_name, result
- )
-
- for addr_type in ADDR_TYPES:
- # Verifying RIB routes
- next_hop = next_hop_per_address_family(
- tgen, dut, peer, addr_type, NEXT_HOP_IP_2
- )
- input_topo = {key: topo["routers"][key] for key in ["r2"]}
- result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol)
- assert result is True, "Testcase {} : Failed \n Error {}".format(
- tc_name, result
- )
-
- logger.info("[Phase 6] : R1 is about to come up now ")
- start_router_daemons(tgen, "r1", ["bgpd"])
-
- logger.info("[Phase 4] : R1 is UP now, so time to collect GR stats ")
+ logger.info("[Phase 4] : R1 is UP and GR state is correct ")
for addr_type in ADDR_TYPES:
result = verify_graceful_restart(
@@ -2142,6 +2118,9 @@ def test_BGP_GR_TC_43_p1(request):
tc_name, result
)
+ # restart the daemon or we get warnings in the follow-on tests
+ start_router_daemons(tgen, "r1", ["bgpd"])
+
write_test_footer(tc_name)
@@ -2432,6 +2411,9 @@ def test_BGP_GR_TC_44_p1(request):
result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol)
assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result)
+ # restart the daemon or we get warnings in the follow-on tests
+ start_router_daemons(tgen, "r2", ["bgpd"])
+
write_test_footer(tc_name)
@@ -2727,6 +2709,9 @@ def test_BGP_GR_TC_45_p1(request):
result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol)
assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result)
+ # restart the daemon or we get warnings in the follow-on tests
+ start_router_daemons(tgen, "r1", ["bgpd"])
+
write_test_footer(tc_name)
diff --git a/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-4.py b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-4.py
index 1166cdc0ef..31aaa0b8a6 100644
--- a/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-4.py
+++ b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-4.py
@@ -763,6 +763,9 @@ def test_BGP_GR_TC_46_p1(request):
tc_name, result
)
+ # restart the daemon or we get warnings in the follow-on tests
+ start_router_daemons(tgen, "r1", ["bgpd"])
+
write_test_footer(tc_name)
@@ -1023,6 +1026,9 @@ def test_BGP_GR_TC_47_p1(request):
tc_name, result
)
+ # restart the daemon or we get warnings in the follow-on tests
+ start_router_daemons(tgen, "r1", ["bgpd"])
+
write_test_footer(tc_name)
@@ -1300,6 +1306,9 @@ def test_BGP_GR_TC_48_p1(request):
tc_name, result
)
+ # restart the daemon or we get warnings in the follow-on tests
+ start_router_daemons(tgen, "r1", ["bgpd"])
+
write_test_footer(tc_name)
diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_up.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_up.py
index eaa6aa4c30..46993c7d9a 100644
--- a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_up.py
+++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_up.py
@@ -62,6 +62,25 @@ else:
"pass",
"Adding {} routes".format(num),
)
+ luCommand(
+ "ce1",
+ 'vtysh -c "show ip route summ" | grep "sharp" | cut -d " " -f 33',
+ str(num),
+ "wait",
+ "See all sharp routes in rib on ce1",
+ wait,
+ wait_time=10,
+ )
+ luCommand(
+ "ce2",
+ 'vtysh -c "show ip route summ" | grep "sharp" | cut -d " " -f 33',
+ str(num),
+ "wait",
+ "See all sharp routes in rib on ce2",
+ wait,
+ wait_time=10,
+ )
+
rtrs = ["ce1", "ce2", "ce3"]
for rtr in rtrs:
luCommand(
diff --git a/tests/topotests/bgp_labeled_unicast_addpath/test_bgp_labeled_unicast_addpath.py b/tests/topotests/bgp_labeled_unicast_addpath/test_bgp_labeled_unicast_addpath.py
index 3af779c427..f4bb487e40 100644
--- a/tests/topotests/bgp_labeled_unicast_addpath/test_bgp_labeled_unicast_addpath.py
+++ b/tests/topotests/bgp_labeled_unicast_addpath/test_bgp_labeled_unicast_addpath.py
@@ -82,53 +82,23 @@ def test_bgp_addpath_labeled_unicast():
r3 = tgen.gears["r3"]
r4 = tgen.gears["r4"]
- def _bgp_check_advertised_routes(prefix_num):
- output = json.loads(
- r3.vtysh_cmd(
- "show bgp ipv4 labeled-unicast neighbors 192.168.34.4 advertised-routes json"
- )
- )
+ def _bgp_check_received_routes(pfxcount):
+ output = json.loads(r4.vtysh_cmd("show bgp ipv4 labeled-unicast summary json"))
expected = {
- "advertisedRoutes": {
- "10.0.0.1/32": {
- "appliedStatusSymbols": {
- "*": True,
- ">": True,
- "=": True,
- }
+ "peers": {
+ "192.168.34.3": {
+ "pfxRcd": pfxcount,
+ "state": "Established",
}
- },
- "totalPrefixCounter": prefix_num,
+ }
}
return topotest.json_cmp(output, expected)
- test_func = functools.partial(_bgp_check_advertised_routes, 2)
+ test_func = functools.partial(_bgp_check_received_routes, 2)
_, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
assert (
result is None
- ), "Failed to advertise labeled-unicast with addpath (multipath)"
-
- def _bgp_check_received_routes():
- output = json.loads(r4.vtysh_cmd("show bgp ipv4 labeled-unicast json"))
- expected = {
- "routes": {
- "10.0.0.1/32": [
- {
- "valid": True,
- "path": "65003 65001",
- },
- {
- "valid": True,
- "path": "65003 65002",
- },
- ]
- }
- }
- return topotest.json_cmp(output, expected)
-
- test_func = functools.partial(_bgp_check_received_routes)
- _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
- assert result is None, "Failed to receive labeled-unicast with addpath (multipath)"
+ ), "Failed to receive labeled-unicast with addpath (multipath=2)"
step("Enable BGP session for R5")
r3.vtysh_cmd(
@@ -139,11 +109,11 @@ def test_bgp_addpath_labeled_unicast():
"""
)
- test_func = functools.partial(_bgp_check_advertised_routes, 3)
+ test_func = functools.partial(_bgp_check_received_routes, 3)
_, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
assert (
result is None
- ), "Failed to advertise labeled-unicast with addpath (multipath)"
+ ), "Failed to receive labeled-unicast with addpath (multipath=3)"
step("Disable BGP session for R5")
r3.vtysh_cmd(
@@ -154,11 +124,11 @@ def test_bgp_addpath_labeled_unicast():
"""
)
- test_func = functools.partial(_bgp_check_advertised_routes, 2)
+ test_func = functools.partial(_bgp_check_received_routes, 2)
_, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
assert (
result is None
- ), "Failed to advertise labeled-unicast with addpath (multipath)"
+ ), "Failed to receive labeled-unicast with addpath (multipath=2)"
if __name__ == "__main__":
diff --git a/tests/topotests/bgp_lu_explicitnull/r1/bgpd.conf b/tests/topotests/bgp_lu_explicitnull/r1/bgpd.conf
new file mode 100644
index 0000000000..a31439c984
--- /dev/null
+++ b/tests/topotests/bgp_lu_explicitnull/r1/bgpd.conf
@@ -0,0 +1,15 @@
+router bgp 65500
+ bgp router-id 192.0.2.1
+ no bgp ebgp-requires-policy
+ bgp labeled-unicast explicit-null
+ neighbor 192.0.2.2 remote-as 65501
+!
+ address-family ipv4 unicast
+ no neighbor 192.0.2.2 activate
+ network 192.168.2.1/32
+ exit-address-family
+ !
+ address-family ipv4 labeled-unicast
+ neighbor 192.0.2.2 activate
+ exit-address-family
+!
diff --git a/tests/topotests/bgp_lu_explicitnull/r1/zebra.conf b/tests/topotests/bgp_lu_explicitnull/r1/zebra.conf
new file mode 100644
index 0000000000..b84574891e
--- /dev/null
+++ b/tests/topotests/bgp_lu_explicitnull/r1/zebra.conf
@@ -0,0 +1,6 @@
+interface r1-eth0
+ ip address 192.0.2.1/24
+!
+interface r1-eth1
+ ip address 192.168.2.1/32
+! \ No newline at end of file
diff --git a/tests/topotests/bgp_lu_explicitnull/r2/bgpd.conf b/tests/topotests/bgp_lu_explicitnull/r2/bgpd.conf
new file mode 100644
index 0000000000..41c2b9b6fa
--- /dev/null
+++ b/tests/topotests/bgp_lu_explicitnull/r2/bgpd.conf
@@ -0,0 +1,15 @@
+router bgp 65501
+ bgp router-id 192.0.2.2
+ no bgp ebgp-requires-policy
+ bgp labeled-unicast explicit-null
+ neighbor 192.0.2.1 remote-as 65500
+!
+ address-family ipv4 unicast
+ no neighbor 192.0.2.1 activate
+ network 192.168.2.2/32
+ exit-address-family
+ !
+ address-family ipv4 labeled-unicast
+ neighbor 192.0.2.1 activate
+ exit-address-family
+!
diff --git a/tests/topotests/bgp_lu_explicitnull/r2/zebra.conf b/tests/topotests/bgp_lu_explicitnull/r2/zebra.conf
new file mode 100644
index 0000000000..9a639610c1
--- /dev/null
+++ b/tests/topotests/bgp_lu_explicitnull/r2/zebra.conf
@@ -0,0 +1,6 @@
+interface r2-eth0
+ ip address 192.0.2.2/24
+!
+interface r2-eth1
+ ip address 192.168.2.2/32
+!
diff --git a/tests/topotests/bgp_lu_explicitnull/test_bgp_lu_explicitnull.py b/tests/topotests/bgp_lu_explicitnull/test_bgp_lu_explicitnull.py
new file mode 100644
index 0000000000..0656e1ed41
--- /dev/null
+++ b/tests/topotests/bgp_lu_explicitnull/test_bgp_lu_explicitnull.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+#
+# test_bgp_explicitnull.py
+#
+# Part of NetDEF Topology Tests
+#
+# Copyright 2023 by 6WIND S.A.
+#
+
+"""
+test_bgp_lu_explicitnull.py: Test BGP LU label allocation
+"""
+
+import os
+import sys
+import json
+import functools
+import pytest
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+
+pytestmark = [pytest.mark.bgpd]
+
+
+# Basic scenario for BGP-LU. Nodes are directly connected.
+# The 192.168.2.2/32 prefix is advertised from r2 to r1
+# The explicit-null label should be used
+# The 192.168.2.1/32 prefix is advertised from r1 to r2
+# The explicit-null label should be used
+# Traffic from 192.168.2.1 to 192.168.2.2 should use explicit-null label
+#
+# AS65500 BGP-LU AS65501
+# +-----+ +-----+
+# | |.1 .2| |
+# | 1 +----------------+ 2 + 192.168.0.2/32
+# | | 192.0.2.0/24 | |
+# +-----+ +-----+
+
+
+def build_topo(tgen):
+ "Build function"
+
+ # Create routers
+ tgen.add_router("r1")
+ tgen.add_router("r2")
+
+ # r1-r2
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r2"])
+
+ # r1
+ switch = tgen.add_switch("s2")
+ switch.add_link(tgen.gears["r1"])
+
+ # r2
+ switch = tgen.add_switch("s3")
+ switch.add_link(tgen.gears["r2"])
+
+
+def setup_module(mod):
+ "Sets up the pytest environment"
+ # This function initiates the topology build with Topogen...
+ tgen = Topogen(build_topo, mod.__name__)
+
+ # Skip if no mpls support
+ if not tgen.hasmpls:
+ logger.info("MPLS is not available, skipping test")
+ pytest.skip("MPLS is not available, skipping")
+ return
+
+ # ... and here it calls Mininet initialization functions.
+ tgen.start_topology()
+
+ # This is a sample of configuration loading.
+ router_list = tgen.routers()
+
+ # Enable mpls input for routers, so we can ping
+ sval = "net.mpls.conf.{}.input"
+ topotest.sysctl_assure(router_list["r2"], sval.format("r2-eth0"), 1)
+ topotest.sysctl_assure(router_list["r1"], sval.format("r1-eth0"), 1)
+
+ # For all registred routers, load the zebra configuration file
+ for rname, router in router_list.items():
+ router.load_config(
+ TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
+ )
+
+ # After loading the configurations, this function loads configured daemons.
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ "Teardown the pytest environment"
+ tgen = get_topogen()
+
+ # This function tears down the whole topology.
+ tgen.stop_topology()
+
+
+def check_show_ip_label_prefix_found(router, ipversion, prefix, label):
+ output = json.loads(
+ router.vtysh_cmd("show {} route {} json".format(ipversion, prefix))
+ )
+ expected = {
+ prefix: [
+ {"prefix": prefix, "nexthops": [{"fib": True, "labels": [int(label)]}]}
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+
+def test_converge_bgplu():
+ "Wait for protocol convergence"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ # tgen.mininet_cli();
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+ # Check r1 gets prefix 192.168.2.2/32
+ test_func = functools.partial(
+ check_show_ip_label_prefix_found,
+ tgen.gears["r1"],
+ "ip",
+ "192.168.2.2/32",
+ "0",
+ )
+ success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5)
+ assert success, "r1, prefix 192.168.2.2/32 from r2 not present"
+
+ # Check r2 gets prefix 192.168.2.1/32
+ test_func = functools.partial(
+ check_show_ip_label_prefix_found,
+ tgen.gears["r2"],
+ "ip",
+ "192.168.2.1/32",
+ "0",
+ )
+ success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5)
+ assert success, "r2, prefix 192.168.2.1/32 from r1 not present"
+
+
+def test_traffic_connectivity():
+ "Wait for protocol convergence"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ def _check_ping(name, dest_addr, src_addr):
+ tgen = get_topogen()
+ output = tgen.gears[name].run(
+ "ping {} -c 1 -w 1 -I {}".format(dest_addr, src_addr)
+ )
+ logger.info(output)
+ if " 0% packet loss" not in output:
+ return True
+
+ logger.info("r1, check ping 192.168.2.2 from 192.168.2.1 is OK")
+ tgen = get_topogen()
+ func = functools.partial(_check_ping, "r1", "192.168.2.2", "192.168.2.1")
+ # tgen.mininet_cli()
+ success, result = topotest.run_and_expect(func, None, count=10, wait=0.5)
+ assert result is None, "r1, ping to 192.168.2.2 from 192.168.2.1 fails"
+
+
+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))
diff --git a/tests/topotests/bgp_node_target_extcommunities/__init__.py b/tests/topotests/bgp_node_target_extcommunities/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/bgp_node_target_extcommunities/__init__.py
diff --git a/tests/topotests/bgp_node_target_extcommunities/r1/frr.conf b/tests/topotests/bgp_node_target_extcommunities/r1/frr.conf
new file mode 100644
index 0000000000..86983387d5
--- /dev/null
+++ b/tests/topotests/bgp_node_target_extcommunities/r1/frr.conf
@@ -0,0 +1,21 @@
+!
+int r1-eth0
+ ip address 192.168.1.1/24
+!
+router bgp 65001
+ bgp router-id 192.168.1.1
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.1.2 remote-as external
+ neighbor 192.168.1.3 remote-as external
+ neighbor 192.168.1.4 remote-as external
+ address-family ipv4 unicast
+ network 10.10.10.10/32
+ neighbor 192.168.1.2 route-map rmap out
+ neighbor 192.168.1.3 route-map rmap out
+ neighbor 192.168.1.4 route-map rmap out
+ exit-address-family
+!
+route-map rmap permit 10
+ set extcommunity nt 192.168.1.3:0 192.168.1.4:0
+exit
diff --git a/tests/topotests/bgp_node_target_extcommunities/r2/frr.conf b/tests/topotests/bgp_node_target_extcommunities/r2/frr.conf
new file mode 100644
index 0000000000..09fda78a3d
--- /dev/null
+++ b/tests/topotests/bgp_node_target_extcommunities/r2/frr.conf
@@ -0,0 +1,8 @@
+!
+int r2-eth0
+ ip address 192.168.1.2/24
+!
+router bgp 65002
+ bgp router-id 192.168.1.2
+ no bgp ebgp-requires-policy
+ neighbor 192.168.1.1 remote-as external
diff --git a/tests/topotests/bgp_node_target_extcommunities/r3/frr.conf b/tests/topotests/bgp_node_target_extcommunities/r3/frr.conf
new file mode 100644
index 0000000000..4883f1f5c2
--- /dev/null
+++ b/tests/topotests/bgp_node_target_extcommunities/r3/frr.conf
@@ -0,0 +1,8 @@
+!
+int r3-eth0
+ ip address 192.168.1.3/24
+!
+router bgp 65003
+ bgp router-id 192.168.1.3
+ no bgp ebgp-requires-policy
+ neighbor 192.168.1.1 remote-as external
diff --git a/tests/topotests/bgp_node_target_extcommunities/r4/frr.conf b/tests/topotests/bgp_node_target_extcommunities/r4/frr.conf
new file mode 100644
index 0000000000..f518bd1fa0
--- /dev/null
+++ b/tests/topotests/bgp_node_target_extcommunities/r4/frr.conf
@@ -0,0 +1,8 @@
+!
+int r4-eth0
+ ip address 192.168.1.4/24
+!
+router bgp 65004
+ bgp router-id 192.168.1.4
+ no bgp ebgp-requires-policy
+ neighbor 192.168.1.1 remote-as external
diff --git a/tests/topotests/bgp_node_target_extcommunities/test_bgp_node_target_extcommunities.py b/tests/topotests/bgp_node_target_extcommunities/test_bgp_node_target_extcommunities.py
new file mode 100644
index 0000000000..23e820b4fc
--- /dev/null
+++ b/tests/topotests/bgp_node_target_extcommunities/test_bgp_node_target_extcommunities.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright (c) 2023 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+"""
+Test if Node Target Extended Communities works.
+
+At r1 we set NT to 192.168.1.3 and 192.168.1.4 (this is the R3/R4 router-id),
+and that means 10.10.10.10/32 MUST be installed on R3 and R4, but not on R2,
+because this route does not have NT:192.168.1.2.
+"""
+
+import os
+import sys
+import json
+import pytest
+import functools
+
+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
+
+pytestmark = [pytest.mark.bgpd]
+
+
+def build_topo(tgen):
+ for routern in range(1, 5):
+ tgen.add_router("r{}".format(routern))
+
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r2"])
+ switch.add_link(tgen.gears["r3"])
+ switch.add_link(tgen.gears["r4"])
+
+
+def setup_module(mod):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+
+ for i, (rname, router) in enumerate(router_list.items(), 1):
+ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_bgp_node_target_extended_communities():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+ r3 = tgen.gears["r3"]
+ r4 = tgen.gears["r4"]
+
+ def _bgp_converge():
+ output = json.loads(r1.vtysh_cmd("show bgp summary json"))
+ expected = {
+ "ipv4Unicast": {
+ "peers": {
+ "192.168.1.2": {
+ "pfxSnt": 1,
+ "state": "Established",
+ },
+ "192.168.1.3": {
+ "pfxSnt": 1,
+ "state": "Established",
+ },
+ "192.168.1.4": {
+ "pfxSnt": 1,
+ "state": "Established",
+ },
+ }
+ }
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_bgp_converge)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Failed announcing 10.10.10.10/32 to r2, r3, and r4"
+
+ def _bgp_check_route(router, exists):
+ output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast json"))
+ if exists:
+ expected = {
+ "routes": {
+ "10.10.10.10/32": [
+ {
+ "valid": True,
+ }
+ ]
+ }
+ }
+ else:
+ expected = {
+ "routes": {
+ "10.10.10.10/32": None,
+ }
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_bgp_check_route, r3, True)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "10.10.10.10/32 is not installed, but SHOULD be"
+
+ test_func = functools.partial(_bgp_check_route, r4, True)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "10.10.10.10/32 is not installed, but SHOULD be"
+
+ test_func = functools.partial(_bgp_check_route, r2, False)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "10.10.10.10/32 is installed, but SHOULD NOT be"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_prefix_list_any/r2/bgpd.conf b/tests/topotests/bgp_prefix_list_any/r2/bgpd.conf
index 77f7c5581b..733205928f 100644
--- a/tests/topotests/bgp_prefix_list_any/r2/bgpd.conf
+++ b/tests/topotests/bgp_prefix_list_any/r2/bgpd.conf
@@ -1,5 +1,5 @@
!
-debug bgp updates
+!debug bgp updates
!
router bgp 65002
no bgp ebgp-requires-policy
diff --git a/tests/topotests/bgp_route_map_delay_timer/__init__.py b/tests/topotests/bgp_route_map_delay_timer/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/bgp_route_map_delay_timer/__init__.py
diff --git a/tests/topotests/bgp_route_map_delay_timer/r1/bgpd.conf b/tests/topotests/bgp_route_map_delay_timer/r1/bgpd.conf
new file mode 100644
index 0000000000..e5325c91bc
--- /dev/null
+++ b/tests/topotests/bgp_route_map_delay_timer/r1/bgpd.conf
@@ -0,0 +1,24 @@
+!
+!debug bgp updates
+!debug bgp neighbor
+!
+bgp route-map delay-timer 5
+!
+router bgp 65001
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.1.2 remote-as external
+ address-family ipv4 unicast
+ network 10.10.10.1/32
+ network 10.10.10.2/32
+ network 10.10.10.3/32
+ aggregate-address 10.10.10.0/24 summary-only
+ neighbor 192.168.1.2 unsuppress-map r2
+ exit-address-family
+!
+ip prefix-list r1 seq 5 permit 10.10.10.1/32
+ip prefix-list r1 seq 10 permit 10.10.10.2/32
+!
+route-map r2 permit 10
+ match ip address prefix-list r1
+exit
diff --git a/tests/topotests/bgp_route_map_delay_timer/r1/zebra.conf b/tests/topotests/bgp_route_map_delay_timer/r1/zebra.conf
new file mode 100644
index 0000000000..b29940f46a
--- /dev/null
+++ b/tests/topotests/bgp_route_map_delay_timer/r1/zebra.conf
@@ -0,0 +1,4 @@
+!
+int r1-eth0
+ ip address 192.168.1.1/24
+!
diff --git a/tests/topotests/bgp_route_map_delay_timer/r2/bgpd.conf b/tests/topotests/bgp_route_map_delay_timer/r2/bgpd.conf
new file mode 100644
index 0000000000..36653e6b1c
--- /dev/null
+++ b/tests/topotests/bgp_route_map_delay_timer/r2/bgpd.conf
@@ -0,0 +1,4 @@
+router bgp 65002
+ no bgp ebgp-requires-policy
+ neighbor 192.168.1.1 remote-as external
+!
diff --git a/tests/topotests/bgp_route_map_delay_timer/r2/zebra.conf b/tests/topotests/bgp_route_map_delay_timer/r2/zebra.conf
new file mode 100644
index 0000000000..cffe827363
--- /dev/null
+++ b/tests/topotests/bgp_route_map_delay_timer/r2/zebra.conf
@@ -0,0 +1,4 @@
+!
+int r2-eth0
+ ip address 192.168.1.2/24
+!
diff --git a/tests/topotests/bgp_route_map_delay_timer/test_bgp_route_map_delay_timer.py b/tests/topotests/bgp_route_map_delay_timer/test_bgp_route_map_delay_timer.py
new file mode 100644
index 0000000000..15a077da2e
--- /dev/null
+++ b/tests/topotests/bgp_route_map_delay_timer/test_bgp_route_map_delay_timer.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright (c) 2023 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+"""
+
+"""
+
+import os
+import sys
+import json
+import pytest
+import functools
+
+pytestmark = pytest.mark.bgpd
+
+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
+
+pytestmark = [pytest.mark.bgpd]
+
+
+def setup_module(mod):
+ topodef = {"s1": ("r1", "r2")}
+ tgen = Topogen(topodef, mod.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+
+ for i, (rname, router) in enumerate(router_list.items(), 1):
+ router.load_config(
+ TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
+ )
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_bgp_route_map_delay_timer():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+
+ def _bgp_converge_1():
+ output = json.loads(
+ r1.vtysh_cmd(
+ "show bgp ipv4 unicast neighbor 192.168.1.2 advertised-routes json"
+ )
+ )
+ expected = {
+ "advertisedRoutes": {
+ "10.10.10.0/24": {},
+ "10.10.10.1/32": {},
+ "10.10.10.2/32": {},
+ "10.10.10.3/32": None,
+ }
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_bgp_converge_1)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
+ assert result is None, "10.10.10.3/32 should not be advertised to r2"
+
+ # Set route-map delay-timer to max value and remove 10.10.10.2/32.
+ # After this, r1 MUST do not announce updates immediately, and wait
+ # 600 seconds before withdrawing 10.10.10.2/32.
+ r2.vtysh_cmd(
+ """
+ configure terminal
+ bgp route-map delay-timer 600
+ no ip prefix-list r1 seq 10 permit 10.10.10.2/32
+ """
+ )
+
+ def _bgp_converge_2():
+ output = json.loads(
+ r1.vtysh_cmd(
+ "show bgp ipv4 unicast neighbor 192.168.1.2 advertised-routes json"
+ )
+ )
+ expected = {
+ "advertisedRoutes": {
+ "10.10.10.0/24": {},
+ "10.10.10.1/32": {},
+ "10.10.10.2/32": None,
+ "10.10.10.3/32": None,
+ }
+ }
+ return topotest.json_cmp(output, expected)
+
+ # We are checking `not None` here to wait count*wait time and if we have different
+ # results than expected, it means good - 10.10.10.2/32 wasn't withdrawn immediately.
+ test_func = functools.partial(_bgp_converge_2)
+ _, result = topotest.run_and_expect(test_func, not None, count=60, wait=0.5)
+ assert (
+ result is not None
+ ), "10.10.10.2/32 advertised, but should not be advertised to r2"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_route_map_vpn_import/r1/bgpd.conf b/tests/topotests/bgp_route_map_vpn_import/r1/bgpd.conf
index c9ad0b1a5b..4aa11ec9d0 100644
--- a/tests/topotests/bgp_route_map_vpn_import/r1/bgpd.conf
+++ b/tests/topotests/bgp_route_map_vpn_import/r1/bgpd.conf
@@ -1,9 +1,9 @@
!
-debug bgp updates
-debug bgp vpn leak-from-vrf
-debug bgp vpn leak-to-vrf
-debug bgp nht
-debug route-map
+!debug bgp updates
+!debug bgp vpn leak-from-vrf
+!debug bgp vpn leak-to-vrf
+!debug bgp nht
+!debug route-map
!
router bgp 65001
bgp router-id 10.10.10.10
diff --git a/tests/topotests/bgp_route_origin_parser/pe1/bgpd.conf b/tests/topotests/bgp_route_origin_parser/pe1/bgpd.conf
new file mode 100644
index 0000000000..1929dfa69b
--- /dev/null
+++ b/tests/topotests/bgp_route_origin_parser/pe1/bgpd.conf
@@ -0,0 +1,2 @@
+router bgp 65001
+ neighbor 192.168.2.1 remote-as external
diff --git a/tests/topotests/bgp_route_origin_parser/test_bgp_route_origin_parser.py b/tests/topotests/bgp_route_origin_parser/test_bgp_route_origin_parser.py
new file mode 100644
index 0000000000..673efc2c73
--- /dev/null
+++ b/tests/topotests/bgp_route_origin_parser/test_bgp_route_origin_parser.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# April 03 2023, Trey Aspelund <taspelund@nvidia.com>
+#
+# Copyright (C) 2023 NVIDIA Corporation
+#
+# Test if the CLI parser for RT/SoO ecoms correctly
+# constrain user input to valid 4-byte ASN values.
+#
+
+import os
+import sys
+import json
+import pytest
+import functools
+
+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.common_config import step
+
+pytestmark = [pytest.mark.bgpd]
+
+
+def build_topo(tgen):
+ tgen.add_router("pe1")
+
+
+def setup_module(mod):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+ pe1 = tgen.gears["pe1"]
+ pe1.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "pe1/bgpd.conf"))
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_bgp_route_origin_parser():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ pe1 = tgen.gears["pe1"]
+
+ def _invalid_soo_accepted():
+ pe1.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65001
+ address-family ipv4 unicast
+ neighbor 192.168.2.1 soo 4294967296:65
+ """
+ )
+ run_cfg = pe1.vtysh_cmd("show run")
+ return "soo" in run_cfg
+
+ def _max_soo_accepted():
+ pe1.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65001
+ address-family ipv4 unicast
+ neighbor 192.168.2.1 soo 4294967295:65
+ """
+ )
+ run_cfg = pe1.vtysh_cmd("show run")
+ return "soo 4294967295:65" in run_cfg
+
+ def _invalid_rt_accepted():
+ pe1.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65001
+ address-family ipv4 unicast
+ rt vpn both 4294967296:65
+ """
+ )
+ run_cfg = pe1.vtysh_cmd("show run")
+ return "rt vpn" in run_cfg
+
+ def _max_rt_accepted():
+ pe1.vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65001
+ address-family ipv4 unicast
+ rt vpn both 4294967295:65
+ """
+ )
+ run_cfg = pe1.vtysh_cmd("show run")
+ return "rt vpn both 4294967295:65" in run_cfg
+
+ step(
+ "Configure invalid 4-byte value SoO (4294967296:65), this should not be accepted"
+ )
+ test_func = functools.partial(_invalid_soo_accepted)
+ _, result = topotest.run_and_expect(test_func, False, count=30, wait=0.5)
+ assert result is False, "invalid 4-byte value of SoO accepted"
+
+ step("Configure max 4-byte value SoO (4294967295:65), this should be accepted")
+ test_func = functools.partial(_max_soo_accepted)
+ _, result = topotest.run_and_expect(test_func, True, count=30, wait=0.5)
+ assert result is True, "max 4-byte value of SoO not accepted"
+
+ step(
+ "Configure invalid 4-byte value RT (4294967296:65), this should not be accepted"
+ )
+ test_func = functools.partial(_invalid_rt_accepted)
+ _, result = topotest.run_and_expect(test_func, False, count=30, wait=0.5)
+ assert result is False, "invalid 4-byte value of RT accepted"
+
+ step("Configure max 4-byte value RT (4294967295:65), this should be accepted")
+ test_func = functools.partial(_max_rt_accepted)
+ _, result = topotest.run_and_expect(test_func, True, count=30, wait=0.5)
+ assert result is True, "max 4-byte value of RT not accepted"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf
index 3512e66cec..cf0013e1b7 100644
--- a/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf
+++ b/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf
@@ -1,5 +1,5 @@
!
-debug bgp updates
+!debug bgp updates
!
router bgp 65002
no bgp ebgp-requires-policy
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce1/bgpd.conf
new file mode 100644
index 0000000000..3459796629
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce1/bgpd.conf
@@ -0,0 +1,8 @@
+frr defaults traditional
+!
+hostname ce1
+password zebra
+!
+log stdout notifications
+log commands
+log file bgpd.log
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce1/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce1/ipv6_rib.json
new file mode 100644
index 0000000000..d19e315772
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce1/ipv6_rib.json
@@ -0,0 +1,58 @@
+{
+ "::/0": [
+ {
+ "prefix": "::/0",
+ "protocol": "static",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 1,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 73,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "ip": "2001:1::1",
+ "afi": "ipv6",
+ "interfaceName": "eth0",
+ "active": true,
+ "weight": 1
+ }
+ ]
+ }
+ ],
+ "2001:1::/64": [
+ {
+ "prefix": "2001:1::/64",
+ "protocol": "connected",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 0,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 8,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "eth0",
+ "active": true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce1/zebra.conf
new file mode 100644
index 0000000000..bb5f93fe52
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce1/zebra.conf
@@ -0,0 +1,16 @@
+log file zebra.log
+!
+hostname ce1
+!
+interface eth0
+ ipv6 address 2001:1::2/64
+ ip address 192.168.1.2/24
+!
+ip forwarding
+ipv6 forwarding
+!
+ipv6 route ::/0 2001:1::1
+ip route 0.0.0.0/0 192.168.1.1
+!
+line vty
+!
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce2/bgpd.conf
new file mode 100644
index 0000000000..8ed9978749
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce2/bgpd.conf
@@ -0,0 +1,8 @@
+frr defaults traditional
+!
+hostname ce2
+password zebra
+!
+log stdout notifications
+log commands
+log file bgpd.log
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce2/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce2/ipv6_rib.json
new file mode 100644
index 0000000000..35ff14efad
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce2/ipv6_rib.json
@@ -0,0 +1,58 @@
+{
+ "::/0": [
+ {
+ "prefix": "::/0",
+ "protocol": "static",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 1,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 73,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "ip": "2001:2::1",
+ "afi": "ipv6",
+ "interfaceName": "eth0",
+ "active": true,
+ "weight": 1
+ }
+ ]
+ }
+ ],
+ "2001:2::/64": [
+ {
+ "prefix": "2001:2::/64",
+ "protocol": "connected",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 0,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 8,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "eth0",
+ "active": true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce2/zebra.conf
new file mode 100644
index 0000000000..a52b83f2dc
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce2/zebra.conf
@@ -0,0 +1,16 @@
+log file zebra.log
+!
+hostname ce2
+!
+interface eth0
+ ipv6 address 2001:2::2/64
+ ip address 192.168.2.2/24
+!
+ip forwarding
+ipv6 forwarding
+!
+ipv6 route ::/0 2001:2::1
+ip route 0.0.0.0/0 192.168.2.1
+!
+line vty
+!
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce3/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce3/bgpd.conf
new file mode 100644
index 0000000000..a85d9701c7
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce3/bgpd.conf
@@ -0,0 +1,8 @@
+frr defaults traditional
+!
+hostname ce3
+password zebra
+!
+log stdout notifications
+log commands
+log file bgpd.log
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce3/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce3/ipv6_rib.json
new file mode 100644
index 0000000000..2f2931f80f
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce3/ipv6_rib.json
@@ -0,0 +1,58 @@
+{
+ "::/0": [
+ {
+ "prefix": "::/0",
+ "protocol": "static",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 1,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 73,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "ip": "2001:3::1",
+ "afi": "ipv6",
+ "interfaceName": "eth0",
+ "active": true,
+ "weight": 1
+ }
+ ]
+ }
+ ],
+ "2001:3::/64": [
+ {
+ "prefix": "2001:3::/64",
+ "protocol": "connected",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 0,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 8,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "eth0",
+ "active": true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce3/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce3/zebra.conf
new file mode 100644
index 0000000000..beca0b1211
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce3/zebra.conf
@@ -0,0 +1,14 @@
+log file zebra.log
+!
+hostname ce3
+!
+interface eth0
+ ipv6 address 2001:3::2/64
+!
+ip forwarding
+ipv6 forwarding
+!
+ipv6 route ::/0 2001:3::1
+!
+line vty
+!
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce4/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce4/bgpd.conf
new file mode 100644
index 0000000000..93fb32fd1b
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce4/bgpd.conf
@@ -0,0 +1,8 @@
+frr defaults traditional
+!
+hostname ce4
+password zebra
+!
+log stdout notifications
+log commands
+log file bgpd.log
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce4/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce4/ipv6_rib.json
new file mode 100644
index 0000000000..8a98768e0d
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce4/ipv6_rib.json
@@ -0,0 +1,58 @@
+{
+ "::/0": [
+ {
+ "prefix": "::/0",
+ "protocol": "static",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 1,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 73,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "ip": "2001:4::1",
+ "afi": "ipv6",
+ "interfaceName": "eth0",
+ "active": true,
+ "weight": 1
+ }
+ ]
+ }
+ ],
+ "2001:4::/64": [
+ {
+ "prefix": "2001:4::/64",
+ "protocol": "connected",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 0,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 8,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "eth0",
+ "active": true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce4/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce4/zebra.conf
new file mode 100644
index 0000000000..7b21074df0
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce4/zebra.conf
@@ -0,0 +1,14 @@
+log file zebra.log
+!
+hostname ce4
+!
+interface eth0
+ ipv6 address 2001:4::2/64
+!
+ip forwarding
+ipv6 forwarding
+!
+ipv6 route ::/0 2001:4::1
+!
+line vty
+!
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce5/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce5/bgpd.conf
new file mode 100644
index 0000000000..2ab6f2d2a7
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce5/bgpd.conf
@@ -0,0 +1,8 @@
+frr defaults traditional
+!
+hostname ce5
+password zebra
+!
+log stdout notifications
+log commands
+log file bgpd.log
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce5/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce5/ipv6_rib.json
new file mode 100644
index 0000000000..80ff52ad6e
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce5/ipv6_rib.json
@@ -0,0 +1,58 @@
+{
+ "::/0": [
+ {
+ "prefix": "::/0",
+ "protocol": "static",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 1,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 73,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "ip": "2001:5::1",
+ "afi": "ipv6",
+ "interfaceName": "eth0",
+ "active": true,
+ "weight": 1
+ }
+ ]
+ }
+ ],
+ "2001:5::/64": [
+ {
+ "prefix": "2001:5::/64",
+ "protocol": "connected",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 0,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 8,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "eth0",
+ "active": true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce5/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce5/zebra.conf
new file mode 100644
index 0000000000..b5ad48e709
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce5/zebra.conf
@@ -0,0 +1,14 @@
+log file zebra.log
+!
+hostname ce5
+!
+interface eth0
+ ipv6 address 2001:5::2/64
+!
+ip forwarding
+ipv6 forwarding
+!
+ipv6 route ::/0 2001:5::1
+!
+line vty
+!
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce6/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce6/bgpd.conf
new file mode 100644
index 0000000000..e0b6540514
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce6/bgpd.conf
@@ -0,0 +1,8 @@
+frr defaults traditional
+!
+hostname ce6
+password zebra
+!
+log stdout notifications
+log commands
+log file bgpd.log
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce6/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce6/ipv6_rib.json
new file mode 100644
index 0000000000..ace6136f06
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce6/ipv6_rib.json
@@ -0,0 +1,58 @@
+{
+ "::/0": [
+ {
+ "prefix": "::/0",
+ "protocol": "static",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 1,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 73,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "ip": "2001:6::1",
+ "afi": "ipv6",
+ "interfaceName": "eth0",
+ "active": true,
+ "weight": 1
+ }
+ ]
+ }
+ ],
+ "2001:6::/64": [
+ {
+ "prefix": "2001:6::/64",
+ "protocol": "connected",
+ "vrfId": 0,
+ "vrfName": "default",
+ "selected": true,
+ "destSelected": true,
+ "distance": 0,
+ "metric": 0,
+ "installed": true,
+ "table": 254,
+ "internalStatus": 16,
+ "internalFlags": 8,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "flags": 3,
+ "fib": true,
+ "directlyConnected": true,
+ "interfaceName": "eth0",
+ "active": true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce6/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce6/zebra.conf
new file mode 100644
index 0000000000..7d19d9880b
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/ce6/zebra.conf
@@ -0,0 +1,14 @@
+log file zebra.log
+!
+hostname ce6
+!
+interface eth0
+ ipv6 address 2001:6::2/64
+!
+ip forwarding
+ipv6 forwarding
+!
+ipv6 route ::/0 2001:6::1
+!
+line vty
+!
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/r1/bgpd.conf
new file mode 100644
index 0000000000..bfc9db960a
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/bgpd.conf
@@ -0,0 +1,79 @@
+frr defaults traditional
+!
+hostname r1
+password zebra
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug bgp neighbor-events
+!debug bgp zebra
+!debug bgp vnc verbose
+!debug bgp update-groups
+!debug bgp updates in
+!debug bgp updates out
+!debug bgp vpn label
+!debug bgp vpn leak-from-vrf
+!debug bgp vpn leak-to-vrf
+!debug bgp vpn rmap-event
+!
+router bgp 1
+ bgp router-id 192.0.2.1
+ no bgp ebgp-requires-policy
+ no bgp default ipv4-unicast
+ neighbor 2001::2 remote-as 2
+ neighbor 2001::2 timers 3 10
+ neighbor 2001::2 timers connect 1
+ neighbor 2001::2 update-source 2001::1
+ neighbor 2001::2 capability extended-nexthop
+ !
+ address-family ipv4 vpn
+ neighbor 2001::2 activate
+ exit-address-family
+ !
+ address-family ipv6 vpn
+ neighbor 2001::2 activate
+ exit-address-family
+ !
+ segment-routing srv6
+ locator loc1
+ !
+!
+router bgp 1 vrf vrf10
+ bgp router-id 192.0.2.1
+ no bgp ebgp-requires-policy
+ !
+ address-family ipv6 unicast
+ sid vpn export auto
+ rd vpn export 1:10
+ rt vpn both 99:99
+ import vpn
+ export vpn
+ redistribute connected
+ exit-address-family
+!
+ address-family ipv4 unicast
+ network 192.168.1.0/24
+ sid vpn export auto
+ rd vpn export 11:10
+ rt vpn both 77:77
+ import vpn
+ export vpn
+ redistribute connected
+ exit-address-family
+!
+router bgp 1 vrf vrf20
+ bgp router-id 192.0.2.1
+ no bgp ebgp-requires-policy
+ no bgp default ipv4-unicast
+ !
+ address-family ipv6 unicast
+ sid vpn export auto
+ rd vpn export 1:20
+ rt vpn both 88:88
+ import vpn
+ export vpn
+ redistribute connected
+ exit-address-family
+!
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json
new file mode 100644
index 0000000000..6fc43e194d
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json
@@ -0,0 +1,169 @@
+{
+ "vrfName": "default",
+ "tableVersion": 2,
+ "routerId": "192.0.2.1",
+ "defaultLocPrf": 100,
+ "localAS": 1,
+ "routes": {
+ "routeDistinguishers": {
+ "1:10": {
+ "2001:1::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:1::",
+ "prefixLen": 64,
+ "network": "2001:1::/64",
+ "metric": 0,
+ "weight": 32768,
+ "peerId": "(unspec)",
+ "path": "",
+ "origin": "incomplete",
+ "announceNexthopSelf": true,
+ "nhVrfName": "vrf10",
+ "nexthops": [
+ {
+ "ip": "::",
+ "hostname": "r1",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ],
+ "2001:3::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:3::",
+ "prefixLen": 64,
+ "network": "2001:3::/64",
+ "metric": 0,
+ "weight": 32768,
+ "peerId": "(unspec)",
+ "path": "",
+ "origin": "incomplete",
+ "announceNexthopSelf": true,
+ "nhVrfName": "vrf10",
+ "nexthops": [
+ {
+ "ip": "::",
+ "hostname": "r1",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ]
+ },
+ "1:20": {
+ "2001:5::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:5::",
+ "prefixLen": 64,
+ "network": "2001:5::/64",
+ "metric": 0,
+ "weight": 32768,
+ "peerId": "(unspec)",
+ "path": "",
+ "origin": "incomplete",
+ "announceNexthopSelf": true,
+ "nhVrfName": "vrf20",
+ "nexthops": [
+ {
+ "ip": "::",
+ "hostname": "r1",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ]
+ },
+ "2:10": {
+ "2001:2::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:2::",
+ "prefixLen": 64,
+ "network": "2001:2::/64",
+ "metric": 0,
+ "weight": 0,
+ "peerId": "2001::2",
+ "path": "2",
+ "origin": "incomplete",
+ "nexthops": [
+ {
+ "ip": "2001::2",
+ "hostname": "r2",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ]
+ },
+ "2:20": {
+ "2001:4::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:4::",
+ "prefixLen": 64,
+ "network": "2001:4::/64",
+ "metric": 0,
+ "weight": 0,
+ "peerId": "2001::2",
+ "path": "2",
+ "origin": "incomplete",
+ "nexthops": [
+ {
+ "ip": "2001::2",
+ "hostname": "r2",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ],
+ "2001:6::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:6::",
+ "prefixLen": 64,
+ "network": "2001:6::/64",
+ "metric": 0,
+ "weight": 0,
+ "peerId": "2001::2",
+ "path": "2",
+ "origin": "incomplete",
+ "nexthops": [
+ {
+ "ip": "2001::2",
+ "hostname": "r2",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_no_sid_rib.json
new file mode 100644
index 0000000000..9783c7e0e6
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_no_sid_rib.json
@@ -0,0 +1,23 @@
+{
+ "192.168.1.0\/24":[
+ {
+ "prefix":"192.168.1.0\/24",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_sid_rib.json
new file mode 100644
index 0000000000..80c1acff8b
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_sid_rib.json
@@ -0,0 +1,53 @@
+{
+ "192.168.1.0\/24":[
+ {
+ "prefix":"192.168.1.0\/24",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "192.168.2.0\/24":[
+ {
+ "prefix":"192.168.2.0\/24",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "labels":[
+ 16
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:1::"
+ }
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_no_sid_rib.json
new file mode 100644
index 0000000000..9783c7e0e6
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_no_sid_rib.json
@@ -0,0 +1,23 @@
+{
+ "192.168.1.0\/24":[
+ {
+ "prefix":"192.168.1.0\/24",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_sid_rib.json
new file mode 100644
index 0000000000..07ca64b45b
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_sid_rib.json
@@ -0,0 +1,54 @@
+{
+ "192.168.1.0\/24":[
+ {
+ "prefix":"192.168.1.0\/24",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "192.168.2.0\/24":[
+ {
+ "prefix":"192.168.2.0\/24",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "ip":"2001::2",
+ "afi":"ipv6",
+ "labels":[
+ 128
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:8::"
+ }
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_no_sid_rib.json
new file mode 100644
index 0000000000..6ac8dac25f
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_no_sid_rib.json
@@ -0,0 +1,77 @@
+{
+ "2001:1::\/64":[
+ {
+ "prefix":"2001:1::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "2001:3::\/64":[
+ {
+ "prefix":"2001:3::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ },
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_sid_rib.json
new file mode 100644
index 0000000000..fac3d1d5f3
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_sid_rib.json
@@ -0,0 +1,107 @@
+{
+ "2001:1::\/64":[
+ {
+ "prefix":"2001:1::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "2001:2::\/64":[
+ {
+ "prefix":"2001:2::\/64",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "labels":[
+ 32
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:2::"
+ }
+ }
+ ]
+ }
+ ],
+ "2001:3::\/64":[
+ {
+ "prefix":"2001:3::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ },
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_no_sid_rib.json
new file mode 100644
index 0000000000..69ce312c4d
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_no_sid_rib.json
@@ -0,0 +1,77 @@
+{
+ "2001:1::\/64":[
+ {
+ "prefix":"2001:1::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "2001:3::\/64":[
+ {
+ "prefix":"2001:3::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ },
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_sid_rib.json
new file mode 100644
index 0000000000..04e230535e
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_sid_rib.json
@@ -0,0 +1,106 @@
+{
+ "2001:1::\/64":[
+ {
+ "prefix":"2001:1::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "2001:2::\/64":[
+ {
+ "prefix":"2001:2::\/64",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "labels":[
+ 128
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:8::"
+ }
+ }
+ ]
+ }
+ ],
+ "2001:3::\/64":[
+ {
+ "prefix":"2001:3::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ },
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_auto_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_auto_sid_rib.json
new file mode 100644
index 0000000000..3cac156bb2
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_auto_sid_rib.json
@@ -0,0 +1,54 @@
+{
+ "192.168.1.0\/24":[
+ {
+ "prefix":"192.168.1.0\/24",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "192.168.2.0\/24":[
+ {
+ "prefix":"192.168.2.0\/24",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "ip":"2001::2",
+ "afi":"ipv6",
+ "labels":[
+ 16
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:1::"
+ }
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_manual_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_manual_sid_rib.json
new file mode 100644
index 0000000000..163e9d626a
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_manual_sid_rib.json
@@ -0,0 +1,53 @@
+{
+ "192.168.1.0\/24":[
+ {
+ "prefix":"192.168.1.0\/24",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "192.168.2.0\/24":[
+ {
+ "prefix":"192.168.2.0\/24",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "ip":"2001::2",
+ "afi":"ipv6",
+ "labels":[
+ 128
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:8::"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_auto_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_auto_sid_rib.json
new file mode 100644
index 0000000000..1313f20c6c
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_auto_sid_rib.json
@@ -0,0 +1,106 @@
+{
+ "2001:1::\/64":[
+ {
+ "prefix":"2001:1::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "2001:2::\/64":[
+ {
+ "prefix":"2001:2::\/64",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "labels":[
+ 16
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:1::"
+ }
+ }
+ ]
+ }
+ ],
+ "2001:3::\/64":[
+ {
+ "prefix":"2001:3::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ },
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_manual_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_manual_sid_rib.json
new file mode 100644
index 0000000000..51f249b184
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_manual_sid_rib.json
@@ -0,0 +1,106 @@
+{
+ "2001:1::\/64":[
+ {
+ "prefix":"2001:1::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "2001:2::\/64":[
+ {
+ "prefix":"2001:2::\/64",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "labels":[
+ 128
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:8::"
+ }
+ }
+ ]
+ }
+ ],
+ "2001:3::\/64":[
+ {
+ "prefix":"2001:3::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ },
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_auto_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_auto_no_sid_rib.json
new file mode 100644
index 0000000000..6ac8dac25f
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_auto_no_sid_rib.json
@@ -0,0 +1,77 @@
+{
+ "2001:1::\/64":[
+ {
+ "prefix":"2001:1::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "2001:3::\/64":[
+ {
+ "prefix":"2001:3::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ },
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_manual_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_manual_no_sid_rib.json
new file mode 100644
index 0000000000..1c3dad089d
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_manual_no_sid_rib.json
@@ -0,0 +1,22 @@
+{
+ "192.168.1.0\/24":[
+ {
+ "prefix":"192.168.1.0\/24",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_rib.json
new file mode 100644
index 0000000000..9579bb15de
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_rib.json
@@ -0,0 +1,112 @@
+{
+ "2001:1::\/64":[
+ {
+ "prefix":"2001:1::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ }
+ ],
+ "2001:2::\/64":[
+ {
+ "prefix":"2001:2::\/64",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "vrf":"default",
+ "active":true,
+ "labels":[
+ 32
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:2::"
+ }
+ }
+ ]
+ }
+ ],
+ "2001:3::\/64":[
+ {
+ "prefix":"2001:3::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ },
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf20_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf20_rib.json
new file mode 100644
index 0000000000..25f146f4b7
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf20_rib.json
@@ -0,0 +1,106 @@
+{
+ "2001:4::\/64":[
+ {
+ "prefix":"2001:4::\/64",
+ "protocol":"bgp",
+ "vrfName":"vrf20",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":20,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "vrf":"default",
+ "active":true,
+ "labels":[
+ 48
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:3::"
+ }
+ }
+ ]
+ }
+ ],
+ "2001:5::\/64":[
+ {
+ "prefix":"2001:5::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf20",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":20,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ }
+ ],
+ "2001:6::\/64":[
+ {
+ "prefix":"2001:6::\/64",
+ "protocol":"bgp",
+ "vrfName":"vrf20",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":20,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "vrf":"default",
+ "active":true,
+ "labels":[
+ 48
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:2:2:3::"
+ }
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf20",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":20,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/r1/zebra.conf
new file mode 100644
index 0000000000..cf31a5c11b
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/zebra.conf
@@ -0,0 +1,43 @@
+log file zebra.log
+!
+hostname r1
+password zebra
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+interface eth0
+ ipv6 address 2001::1/64
+!
+interface eth1 vrf vrf10
+ ipv6 address 2001:1::1/64
+ ip address 192.168.1.1/24
+!
+interface eth2 vrf vrf10
+ ipv6 address 2001:3::1/64
+!
+interface eth3 vrf vrf20
+ ipv6 address 2001:5::1/64
+!
+segment-routing
+ srv6
+ locators
+ locator loc1
+ prefix 2001:db8:1:1::/64 block-len 40 node-len 24 func-bits 16
+ !
+ !
+!
+ip forwarding
+ipv6 forwarding
+!
+ipv6 route 2001:db8:2:1::/64 2001::2
+ipv6 route 2001:db8:2:2::/64 2001::2
+ipv6 route 2001:db8:2:3::/64 2001::2
+!
+line vty
+!
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/r2/bgpd.conf
new file mode 100644
index 0000000000..892a9f73e5
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/bgpd.conf
@@ -0,0 +1,80 @@
+frr defaults traditional
+!
+hostname r2
+password zebra
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug bgp neighbor-events
+!debug bgp zebra
+!debug bgp vnc verbose
+!debug bgp update-groups
+!debug bgp updates in
+!debug bgp updates out
+!debug bgp updates
+!debug bgp vpn label
+!debug bgp vpn leak-from-vrf
+!debug bgp vpn leak-to-vrf
+!debug bgp vpn rmap-event
+!
+router bgp 2
+ bgp router-id 192.0.2.2
+ no bgp ebgp-requires-policy
+ no bgp default ipv4-unicast
+ neighbor 2001::1 remote-as 1
+ neighbor 2001::1 update-source 2001::2
+ neighbor 2001::1 timers 3 10
+ neighbor 2001::1 timers connect 1
+ neighbor 2001::1 capability extended-nexthop
+ !
+ address-family ipv4 vpn
+ neighbor 2001::1 activate
+ exit-address-family
+ !
+ address-family ipv6 vpn
+ neighbor 2001::1 activate
+ exit-address-family
+ !
+ segment-routing srv6
+ locator loc1
+ !
+!
+router bgp 2 vrf vrf10
+ bgp router-id 192.0.2.2
+ no bgp ebgp-requires-policy
+ !
+ address-family ipv6 unicast
+ sid vpn export auto
+ rd vpn export 2:10
+ rt vpn both 99:99
+ import vpn
+ export vpn
+ redistribute connected
+ exit-address-family
+!
+ address-family ipv4 unicast
+ network 192.168.2.0/24
+ sid vpn export auto
+ rd vpn export 22:10
+ rt vpn both 77:77
+ import vpn
+ export vpn
+ redistribute connected
+ exit-address-family
+!
+router bgp 2 vrf vrf20
+ bgp router-id 192.0.2.2
+ no bgp ebgp-requires-policy
+ no bgp default ipv4-unicast
+ !
+ address-family ipv6 unicast
+ sid vpn export auto
+ rd vpn export 2:20
+ rt vpn both 88:88
+ import vpn
+ export vpn
+ redistribute connected
+ exit-address-family
+!!
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json
new file mode 100644
index 0000000000..538e8955ef
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json
@@ -0,0 +1,169 @@
+{
+ "vrfName": "default",
+ "tableVersion": 2,
+ "routerId": "192.0.2.2",
+ "defaultLocPrf": 100,
+ "localAS": 2,
+ "routes": {
+ "routeDistinguishers": {
+ "1:10": {
+ "2001:1::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:1::",
+ "prefixLen": 64,
+ "network": "2001:1::/64",
+ "metric": 0,
+ "weight": 0,
+ "peerId": "2001::1",
+ "path": "1",
+ "origin": "incomplete",
+ "nexthops": [
+ {
+ "ip": "2001::1",
+ "hostname": "r1",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ],
+ "2001:3::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:3::",
+ "prefixLen": 64,
+ "network": "2001:3::/64",
+ "metric": 0,
+ "weight": 0,
+ "peerId": "2001::1",
+ "path": "1",
+ "origin": "incomplete",
+ "nexthops": [
+ {
+ "ip": "2001::1",
+ "hostname": "r1",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ]
+ },
+ "1:20": {
+ "2001:5::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:5::",
+ "prefixLen": 64,
+ "network": "2001:5::/64",
+ "metric": 0,
+ "weight": 0,
+ "peerId": "2001::1",
+ "path": "1",
+ "origin": "incomplete",
+ "nexthops": [
+ {
+ "ip": "2001::1",
+ "hostname": "r1",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ]
+ },
+ "2:10": {
+ "2001:2::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:2::",
+ "prefixLen": 64,
+ "network": "2001:2::/64",
+ "metric": 0,
+ "weight": 32768,
+ "peerId": "(unspec)",
+ "path": "",
+ "origin": "incomplete",
+ "announceNexthopSelf": true,
+ "nhVrfName": "vrf10",
+ "nexthops": [
+ {
+ "ip": "::",
+ "hostname": "r2",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ]
+ },
+ "2:20": {
+ "2001:4::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:4::",
+ "prefixLen": 64,
+ "network": "2001:4::/64",
+ "metric": 0,
+ "weight": 32768,
+ "peerId": "(unspec)",
+ "path": "",
+ "origin": "incomplete",
+ "announceNexthopSelf": true,
+ "nhVrfName": "vrf20",
+ "nexthops": [
+ {
+ "ip": "::",
+ "hostname": "r2",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ],
+ "2001:6::/64": [
+ {
+ "valid": true,
+ "bestpath": true,
+ "selectionReason": "First path received",
+ "pathFrom": "external",
+ "prefix": "2001:6::",
+ "prefixLen": 64,
+ "network": "2001:6::/64",
+ "metric": 0,
+ "weight": 32768,
+ "peerId": "(unspec)",
+ "path": "",
+ "origin": "incomplete",
+ "announceNexthopSelf": true,
+ "nhVrfName": "vrf20",
+ "nexthops": [
+ {
+ "ip": "::",
+ "hostname": "r2",
+ "afi": "ipv6",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf10_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf10_rib.json
new file mode 100644
index 0000000000..446bb8eb3c
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf10_rib.json
@@ -0,0 +1,106 @@
+{
+ "2001:1::\/64":[
+ {
+ "prefix":"2001:1::\/64",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "vrf":"default",
+ "active":true,
+ "labels":[
+ 32
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:1:1:2::"
+ }
+ }
+ ]
+ }
+ ],
+ "2001:2::\/64":[
+ {
+ "prefix":"2001:2::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ }
+ ],
+ "2001:3::\/64":[
+ {
+ "prefix":"2001:3::\/64",
+ "protocol":"bgp",
+ "vrfName":"vrf10",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "vrf":"default",
+ "active":true,
+ "labels":[
+ 32
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:1:1:2::"
+ }
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf10",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":10,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf20_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf20_rib.json
new file mode 100644
index 0000000000..8bc2fc23f1
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf20_rib.json
@@ -0,0 +1,112 @@
+{
+ "2001:4::\/64":[
+ {
+ "prefix":"2001:4::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf20",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":20,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ }
+ ],
+ "2001:5::\/64":[
+ {
+ "prefix":"2001:5::\/64",
+ "protocol":"bgp",
+ "vrfName":"vrf20",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":0,
+ "installed":true,
+ "table":20,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "afi":"ipv6",
+ "vrf":"default",
+ "active":true,
+ "labels":[
+ 48
+ ],
+ "weight":1,
+ "seg6local":{
+ "action":"unspec"
+ },
+ "seg6":{
+ "segs":"2001:db8:1:1:3::"
+ }
+ }
+ ]
+ }
+ ],
+ "2001:6::\/64":[
+ {
+ "prefix":"2001:6::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf20",
+ "selected":true,
+ "destSelected":true,
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":20,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ }
+ ],
+ "fe80::\/64":[
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf20",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":20,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ },
+ {
+ "prefix":"fe80::\/64",
+ "protocol":"connected",
+ "vrfName":"vrf20",
+ "distance":0,
+ "metric":0,
+ "installed":true,
+ "table":20,
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "directlyConnected":true,
+ "active":true
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/r2/zebra.conf
new file mode 100644
index 0000000000..9771ee1cd7
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/zebra.conf
@@ -0,0 +1,43 @@
+log file zebra.log
+!
+hostname r2
+password zebra
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+interface eth0
+ ipv6 address 2001::2/64
+!
+interface eth1 vrf vrf10
+ ipv6 address 2001:2::1/64
+ ip address 192.168.2.1/24
+!
+interface eth2 vrf vrf20
+ ipv6 address 2001:4::1/64
+!
+interface eth3 vrf vrf20
+ ipv6 address 2001:6::1/64
+!
+segment-routing
+ srv6
+ locators
+ locator loc1
+ prefix 2001:db8:2:2::/64 block-len 40 node-len 24 func-bits 16
+ !
+ !
+!
+ip forwarding
+ipv6 forwarding
+!
+ipv6 route 2001:db8:1:1::/64 2001::1
+ipv6 route 2001:db8:1:2::/64 2001::1
+ipv6 route 2001:db8:1:3::/64 2001::1
+!
+line vty
+!
diff --git a/tests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py b/tests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py
new file mode 100755
index 0000000000..cddcf6a9a1
--- /dev/null
+++ b/tests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py
@@ -0,0 +1,453 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+#
+# Copyright 2023 6WIND S.A.
+# Authored by Dmytro Shytyi <dmytro.shytyi@6wind.com>
+#
+# 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.
+#
+
+import os
+import re
+import sys
+import json
+import functools
+import pytest
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+from lib.common_config import required_linux_kernel_version
+
+
+def build_topo(tgen):
+ """
+ CE1 CE3 CE5
+ (eth0) (eth0) (eth0)
+ :2 :2 :2
+ | | |
+ 192.168.1.0 | |
+ /24 | |
+ 2001: 2001: 2001:
+ 1::/64 3::/64 5::/64
+ | | |
+ :1 :1 :1
+ +-(eth1)--(eth2)---(eth3)-+
+ | \ / | |
+ | (vrf10) (vrf20) |
+ | R1 |
+ +----------(eth0)---------+
+ :1
+ |
+ 2001::/64
+ |
+ :2
+ (eth0)
+ +----------(eth0)--------------+
+ | R2 |
+ | (vrf10) (vrf20) |
+ | / / \ |
+ +-(eth1)-----(eth2)-----(eth3)-+
+ :1 :1 :1
+ | | |
+ +------+ +------+ +------+
+ / 2001: \ / 2001: \ / 2001: \
+ / 2::/64 \ 4::/64 / \ 6::/64 /
+ /192.168.2.0| / \ /
+ \ /24 / \ | | |
+ +------+ +------+ +------+
+ | | |
+ :2 :2 :2
+ (eth0) (eth0) (eth0)
+ CE2 CE4 CE6
+ """
+
+ tgen.add_router("r1")
+ tgen.add_router("r2")
+ tgen.add_router("ce1")
+ tgen.add_router("ce2")
+ tgen.add_router("ce3")
+ tgen.add_router("ce4")
+ tgen.add_router("ce5")
+ tgen.add_router("ce6")
+
+ tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "eth0", "eth0")
+ tgen.add_link(tgen.gears["ce1"], tgen.gears["r1"], "eth0", "eth1")
+ tgen.add_link(tgen.gears["ce2"], tgen.gears["r2"], "eth0", "eth1")
+ tgen.add_link(tgen.gears["ce3"], tgen.gears["r1"], "eth0", "eth2")
+ tgen.add_link(tgen.gears["ce4"], tgen.gears["r2"], "eth0", "eth2")
+ tgen.add_link(tgen.gears["ce5"], tgen.gears["r1"], "eth0", "eth3")
+ tgen.add_link(tgen.gears["ce6"], tgen.gears["r2"], "eth0", "eth3")
+
+
+def setup_module(mod):
+ result = required_linux_kernel_version("5.11")
+ if result is not True:
+ pytest.skip("Kernel requirements are not met")
+
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+ router_list = tgen.routers()
+ for i, (rname, router) in enumerate(tgen.routers().items(), 1):
+ router.load_config(
+ TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
+ )
+
+ tgen.gears["r1"].run("modprobe vrf")
+ tgen.gears["r1"].run("ip link add vrf10 type vrf table 10")
+ tgen.gears["r1"].run("ip link set vrf10 up")
+ tgen.gears["r1"].run("ip link add vrf20 type vrf table 20")
+ tgen.gears["r1"].run("ip link set vrf20 up")
+ tgen.gears["r1"].run("ip link set eth1 master vrf10")
+ tgen.gears["r1"].run("ip link set eth2 master vrf10")
+ tgen.gears["r1"].run("ip link set eth3 master vrf20")
+ tgen.gears["r1"].run("sysctl net.vrf.strict_mode=1")
+ tgen.gears["r1"].run("sysctl net.ipv4.conf.default.rp_filter=0")
+ tgen.gears["r1"].run("sysctl net.ipv4.conf.all.rp_filter=0")
+ tgen.gears["r1"].run("sysctl net.ipv4.conf.lo.rp_filter=0")
+ tgen.gears["r1"].run("sysctl net.ipv4.conf.eth0.rp_filter=0")
+ tgen.gears["r1"].run("sysctl net.ipv4.conf.eth1.rp_filter=0")
+ tgen.gears["r1"].run("sysctl net.ipv4.conf.vrf10.rp_filter=0")
+
+ tgen.gears["r2"].run("modprobe vrf")
+ tgen.gears["r2"].run("ip link add vrf10 type vrf table 10")
+ tgen.gears["r2"].run("ip link set vrf10 up")
+ tgen.gears["r2"].run("ip link add vrf20 type vrf table 20")
+ tgen.gears["r2"].run("ip link set vrf20 up")
+ tgen.gears["r2"].run("ip link set eth1 master vrf10")
+ tgen.gears["r2"].run("ip link set eth2 master vrf20")
+ tgen.gears["r2"].run("ip link set eth3 master vrf20")
+ tgen.gears["r2"].run("sysctl net.vrf.strict_mode=1")
+ tgen.gears["r2"].run("sysctl net.ipv4.conf.default.rp_filter=0")
+ tgen.gears["r2"].run("sysctl net.ipv4.conf.all.rp_filter=0")
+ tgen.gears["r2"].run("sysctl net.ipv4.conf.lo.rp_filter=0")
+ tgen.gears["r2"].run("sysctl net.ipv4.conf.eth0.rp_filter=0")
+ tgen.gears["r2"].run("sysctl net.ipv4.conf.eth1.rp_filter=0")
+ tgen.gears["r2"].run("sysctl net.ipv4.conf.vrf10.rp_filter=0")
+ tgen.start_router()
+
+ # FOR DEVELOPER:
+ # If you want to stop some specific line and start interactive shell,
+ # please use tgen.mininet_cli() to start it.
+ # Example:
+ # tgen=get_topogen()
+ # tgen.mininet_cli()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def open_json_file(filename):
+ try:
+ with open(filename, "r") as f:
+ return json.load(f)
+ except IOError:
+ assert False, "Could not read file {}".format(filename)
+
+
+def check_ping(name, dest_addr, expect_connected):
+ def _check(name, dest_addr, match):
+ tgen = get_topogen()
+ output = tgen.gears[name].run("ping {} -c 1 -w 1".format(dest_addr))
+ logger.info(output)
+ if match not in output:
+ return True
+
+ match = ", {} packet loss".format("0%" if expect_connected else "100%")
+ logger.info("[+] check {} {} {}".format(name, dest_addr, match))
+ tgen = get_topogen()
+ func = functools.partial(_check, name, dest_addr, match)
+ success, result = topotest.run_and_expect(func, None, count=10, wait=0.5)
+ assert result is None, "Failed"
+
+
+def check_rib(name, cmd, expected_file):
+ def _check(name, cmd, expected_file):
+ logger.info("polling")
+ tgen = get_topogen()
+ router = tgen.gears[name]
+ output = json.loads(router.vtysh_cmd(cmd))
+ expected = open_json_file("{}/{}".format(CWD, expected_file))
+ return topotest.json_cmp(output, expected)
+
+ logger.info('[+] check {} "{}" {}'.format(name, cmd, expected_file))
+ tgen = get_topogen()
+ func = functools.partial(_check, name, cmd, expected_file)
+ success, result = topotest.run_and_expect(func, None, count=10, wait=0.5)
+ assert result is None, "Failed"
+
+
+def test_rib():
+ check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib.json")
+ check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib.json")
+ check_rib("r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_rib.json")
+ check_rib("r1", "show ipv6 route vrf vrf20 json", "r1/vrf20_rib.json")
+ check_rib("r2", "show ipv6 route vrf vrf10 json", "r2/vrf10_rib.json")
+ check_rib("r2", "show ipv6 route vrf vrf20 json", "r2/vrf20_rib.json")
+ check_rib("ce1", "show ipv6 route json", "ce1/ipv6_rib.json")
+ check_rib("ce2", "show ipv6 route json", "ce2/ipv6_rib.json")
+ check_rib("ce3", "show ipv6 route json", "ce3/ipv6_rib.json")
+ check_rib("ce4", "show ipv6 route json", "ce4/ipv6_rib.json")
+ check_rib("ce5", "show ipv6 route json", "ce5/ipv6_rib.json")
+ check_rib("ce6", "show ipv6 route json", "ce6/ipv6_rib.json")
+
+
+def test_ping():
+ check_ping("ce1", "2001:2::2", True)
+ check_ping("ce1", "2001:3::2", True)
+ check_ping("ce1", "2001:4::2", False)
+ check_ping("ce1", "2001:5::2", False)
+ check_ping("ce1", "2001:6::2", False)
+ check_ping("ce4", "2001:1::2", False)
+ check_ping("ce4", "2001:2::2", False)
+ check_ping("ce4", "2001:3::2", False)
+ check_ping("ce4", "2001:5::2", True)
+ check_ping("ce4", "2001:6::2", True)
+
+
+def test_sid_per_afv6_auto():
+ check_rib("r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_auto_sid_rib.json")
+ check_ping("ce1", "2001:2::2", True)
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ address-family ipv6 unicast
+ no sid vpn export auto
+ """
+ )
+ check_rib(
+ "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_auto_no_sid_rib.json"
+ )
+ check_ping("ce1", "2001:2::2", False)
+
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ address-family ipv6 unicast
+ sid vpn export auto
+ """
+ )
+ check_rib("r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_auto_sid_rib.json")
+ check_ping("ce1", "2001:2::2", True)
+
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ address-family ipv6 unicast
+ no sid vpn export auto
+ """
+ )
+ check_rib(
+ "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_auto_no_sid_rib.json"
+ )
+ check_ping("ce1", "2001:2::2", False)
+
+
+def test_sid_per_afv6_manual():
+ check_rib(
+ "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_manual_no_sid_rib.json"
+ )
+ check_ping("ce1", "2001:2::2", False)
+
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ address-family ipv6 unicast
+ sid vpn export 8
+ """
+ )
+
+ check_rib(
+ "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_manual_sid_rib.json"
+ )
+ check_ping("ce1", "2001:2::2", True)
+
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ address-family ipv6 unicast
+ no sid vpn export 8
+ """
+ )
+ check_rib(
+ "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_manual_no_sid_rib.json"
+ )
+ check_ping("ce1", "2001:2::2", False)
+
+
+def test_sid_per_afv4_auto():
+ check_rib("r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_auto_sid_rib.json")
+ check_ping("ce1", "192.168.2.2", True)
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ address-family ipv4 unicast
+ no sid vpn export auto
+ """
+ )
+
+ check_rib(
+ "r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_auto_no_sid_rib.json"
+ )
+ check_ping("ce1", "192.168.2.2", False)
+
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ address-family ipv4 unicast
+ sid vpn export auto
+ """
+ )
+
+ check_rib("r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_auto_sid_rib.json")
+ check_ping("ce1", "192.168.2.2", True)
+
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ address-family ipv4 unicast
+ no sid vpn export auto
+ """
+ )
+ check_rib(
+ "r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_auto_no_sid_rib.json"
+ )
+ check_ping("ce1", "192.168.2.2", False)
+
+
+def test_sid_per_afv4_manual():
+ check_rib(
+ "r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_manual_no_sid_rib.json"
+ )
+ check_ping("ce1", "192.168.2.2", False)
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ address-family ipv4 unicast
+ sid vpn export 8
+ """
+ )
+
+ check_rib("r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_manual_sid_rib.json")
+ check_ping("ce1", "192.168.2.2", True)
+
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ address-family ipv4 unicast
+ no sid vpn export 8
+ """
+ )
+ check_ping("ce1", "192.168.2.2", False)
+ check_rib(
+ "r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_manual_no_sid_rib.json"
+ )
+
+
+def test_sid_per_vrf_auto():
+ check_rib(
+ "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_pervrf_auto_no_sid_rib.json"
+ )
+ check_ping("ce1", "2001:2::2", False)
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ sid vpn per-vrf export auto
+ """
+ )
+
+ check_rib(
+ "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_pervrf6_auto_sid_rib.json"
+ )
+ check_ping("ce1", "2001:2::2", True)
+ check_rib(
+ "r1", "show ip route vrf vrf10 json", "r1/vrf10_pervrf4_auto_sid_rib.json"
+ )
+ check_ping("ce1", "192.168.2.2", True)
+
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ no sid vpn per-vrf export auto
+ """
+ )
+
+ check_rib(
+ "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_pervrf_auto_no_sid_rib.json"
+ )
+ check_ping("ce1", "2001:2::2", False)
+
+
+def test_sid_per_vrf_manual():
+ check_rib(
+ "r1", "show ip route vrf vrf10 json", "r1/vrf10_pervrf_manual_no_sid_rib.json"
+ )
+ check_ping("ce1", "192.168.2.2", False)
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ sid vpn per-vrf export 8
+ """
+ )
+
+ check_rib(
+ "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_pervrf6_manual_sid_rib.json"
+ )
+ check_ping("ce1", "2001:2::2", True)
+ check_rib(
+ "r1", "show ip route vrf vrf10 json", "r1/vrf10_pervrf4_manual_sid_rib.json"
+ )
+ check_ping("ce1", "192.168.2.2", True)
+
+ get_topogen().gears["r2"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 2 vrf vrf10
+ no sid vpn per-vrf export 8
+ """
+ )
+
+ check_rib(
+ "r1", "show ip route vrf vrf10 json", "r1/vrf10_pervrf_manual_no_sid_rib.json"
+ )
+ check_ping("ce1", "192.168.2.2", False)
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/zebra.conf
index a9319a6aed..cbc5ce1f09 100644
--- a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/zebra.conf
+++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/zebra.conf
@@ -7,9 +7,9 @@ log stdout notifications
log monitor notifications
log commands
!
-debug zebra packet
-debug zebra dplane
-debug zebra kernel
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
!
interface eth0
ipv6 address 2001::1/64
diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/zebra.conf
index 9e5fa0ac07..449ca74d5e 100644
--- a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/zebra.conf
+++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/zebra.conf
@@ -7,9 +7,9 @@ log stdout notifications
log monitor notifications
log commands
!
-debug zebra packet
-debug zebra dplane
-debug zebra kernel
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
!
interface eth0
ipv6 address 2001::2/64
diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/zebra.conf
index 2c560dfc06..f913b9f002 100644
--- a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/zebra.conf
+++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/zebra.conf
@@ -7,9 +7,9 @@ log stdout notifications
log monitor notifications
log commands
!
-debug zebra packet
-debug zebra dplane
-debug zebra kernel
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
!
interface eth0
ipv6 address 2001::1/64
diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/zebra.conf
index b9277a9a8c..201d0cce23 100644
--- a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/zebra.conf
+++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/zebra.conf
@@ -7,9 +7,9 @@ log stdout notifications
log monitor notifications
log commands
!
-debug zebra packet
-debug zebra dplane
-debug zebra kernel
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
!
interface eth0
ipv6 address 2001::2/64
diff --git a/tests/topotests/bgp_suppress_fib/r2/bgpd.allowas_in.conf b/tests/topotests/bgp_suppress_fib/r2/bgpd.allowas_in.conf
index caebb0e922..fb6980a139 100644
--- a/tests/topotests/bgp_suppress_fib/r2/bgpd.allowas_in.conf
+++ b/tests/topotests/bgp_suppress_fib/r2/bgpd.allowas_in.conf
@@ -2,12 +2,12 @@ access-list access seq 10 permit 192.168.1.1/32
!
ip route 192.168.1.1/32 10.0.0.10
!
-debug bgp bestpath
-debug bgp nht
-debug bgp updates
-debug bgp update-groups
-debug bgp zebra
-debug zebra rib detail
+!debug bgp bestpath
+!debug bgp nht
+!debug bgp updates
+!debug bgp update-groups
+!debug bgp zebra
+!debug zebra rib detail
!
router bgp 2
address-family ipv4 uni
diff --git a/tests/topotests/bgp_suppress_fib/r2/bgpd.conf b/tests/topotests/bgp_suppress_fib/r2/bgpd.conf
index 010e86aad7..129b812036 100644
--- a/tests/topotests/bgp_suppress_fib/r2/bgpd.conf
+++ b/tests/topotests/bgp_suppress_fib/r2/bgpd.conf
@@ -1,6 +1,6 @@
-debug bgp updates
-debug bgp bestpath 40.0.0.0/8
-debug bgp zebra
+!debug bgp updates
+!debug bgp bestpath 40.0.0.0/8
+!debug bgp zebra
!
router bgp 2
no bgp ebgp-requires-policy
@@ -8,4 +8,4 @@ router bgp 2
neighbor 10.0.0.1 remote-as 1
neighbor 10.0.0.10 remote-as 3
address-family ipv4 uni
- network 60.0.0.0/24 \ No newline at end of file
+ network 60.0.0.0/24
diff --git a/tests/topotests/bgp_unique_rid/test_bgp_unique_rid.py b/tests/topotests/bgp_unique_rid/test_bgp_unique_rid.py
index 47b2452b81..f89f3378fb 100644
--- a/tests/topotests/bgp_unique_rid/test_bgp_unique_rid.py
+++ b/tests/topotests/bgp_unique_rid/test_bgp_unique_rid.py
@@ -847,8 +847,6 @@ def test_bgp_unique_rid_chaos4_p2():
for intf in topo["routers"][rtr]["links"].keys():
topo1["routers"][rtr]["links"][intf].pop("ipv4")
topo1["routers"][rtr]["links"][intf].pop("ipv6")
- if intf is "lo":
- topo1["routers"][rtr]["links"][intf].pop("ipv4")
build_config_from_json(tgen, topo1, save_bkup=False)
diff --git a/tests/topotests/bgp_vrf_md5_peering/r1/bgpd.conf b/tests/topotests/bgp_vrf_md5_peering/r1/bgpd.conf
index 8d8f64158f..9f2ee19357 100644
--- a/tests/topotests/bgp_vrf_md5_peering/r1/bgpd.conf
+++ b/tests/topotests/bgp_vrf_md5_peering/r1/bgpd.conf
@@ -1,5 +1,5 @@
!
-debug bgp neighbor
+!debug bgp neighbor
!
router bgp 65534 vrf public
bgp router-id 10.0.0.1
diff --git a/tests/topotests/config_timing/test_config_timing.py b/tests/topotests/config_timing/test_config_timing.py
index 8de6f9bf70..5c1b97262c 100644
--- a/tests/topotests/config_timing/test_config_timing.py
+++ b/tests/topotests/config_timing/test_config_timing.py
@@ -133,7 +133,7 @@ def test_static_timing():
delta = (datetime.datetime.now() - tstamp).total_seconds()
tot_delta += delta
- router.logger.info(
+ router.logger.debug(
"\nvtysh command => {}\nvtysh output <= {}\nin {}s".format(
load_command, output, delta
)
@@ -152,7 +152,7 @@ def test_static_timing():
# Number of static routes
router = tgen.gears["r1"]
- output = router.run("vtysh -h | grep address-sanitizer")
+ output = router.net.cmd_legacy("vtysh -h | grep address-sanitizer", warn=False)
if output == "":
logger.info("No Address Sanitizer, generating 10000 routes")
prefix_count = 10000
diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py
index df5d066023..74e308dbc6 100755
--- a/tests/topotests/conftest.py
+++ b/tests/topotests/conftest.py
@@ -1,27 +1,44 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
"""
Topotest conftest.py file.
"""
# pylint: disable=consider-using-f-string
import glob
+import logging
import os
-import pdb
import re
+import resource
import subprocess
import sys
import time
-import resource
-import pytest
import lib.fixtures
-from lib import topolog
-from lib.micronet import Commander, proc_error
-from lib.micronet_cli import cli
-from lib.micronet_compat import Mininet, cleanup_current, cleanup_previous
+import pytest
+from lib.micronet_compat import Mininet
from lib.topogen import diagnose_env, get_topogen
-from lib.topolog import logger
-from lib.topotest import g_extra_config as topotest_extra_config
+from lib.topolog import get_test_logdir, logger
from lib.topotest import json_cmp_result
+from munet import cli
+from munet.base import Commander, proc_error
+from munet.cleanup import cleanup_current, cleanup_previous
+from munet.config import ConfigOptionsProxy
+from munet.testing.util import pause_test
+
+from lib import topolog, topotest
+
+try:
+ # Used by munet native tests
+ from munet.testing.fixtures import event_loop, unet # pylint: disable=all # noqa
+
+ @pytest.fixture(scope="module")
+ def rundir_module(pytestconfig):
+ d = os.path.join(pytestconfig.option.rundir, get_test_logdir())
+ logging.debug("rundir_module: test module rundir %s", d)
+ return d
+
+except (AttributeError, ImportError):
+ pass
def pytest_addoption(parser):
@@ -60,12 +77,28 @@ def pytest_addoption(parser):
)
parser.addoption(
+ "--logd",
+ action="append",
+ metavar="DAEMON[,ROUTER[,...]",
+ help=(
+ "Tail-F the DAEMON log file on all or a subset of ROUTERs."
+ " Option can be given multiple times."
+ ),
+ )
+
+ parser.addoption(
"--pause",
action="store_true",
help="Pause after each test",
)
parser.addoption(
+ "--pause-at-end",
+ action="store_true",
+ help="Pause before taking munet down",
+ )
+
+ parser.addoption(
"--pause-on-error",
action="store_true",
help="Do not pause after (disables default when --shell or -vtysh given)",
@@ -78,6 +111,30 @@ def pytest_addoption(parser):
help="Do not pause after (disables default when --shell or -vtysh given)",
)
+ parser.addoption(
+ "--pcap",
+ default="",
+ metavar="NET[,NET...]",
+ help="Comma-separated list of networks to capture packets on, or 'all'",
+ )
+
+ parser.addoption(
+ "--perf",
+ action="append",
+ metavar="DAEMON[,ROUTER[,...]",
+ help=(
+ "Collect performance data from given DAEMON on all or a subset of ROUTERs."
+ " Option can be given multiple times."
+ ),
+ )
+
+ parser.addoption(
+ "--perf-options",
+ metavar="OPTS",
+ default="-g",
+ help="Options to pass to `perf record`.",
+ )
+
rundir_help = "directory for running in and log files"
parser.addini("rundir", rundir_help, default="/tmp/topotests")
parser.addoption("--rundir", metavar="DIR", help=rundir_help)
@@ -133,7 +190,7 @@ def pytest_addoption(parser):
def check_for_memleaks():
- assert topotest_extra_config["valgrind_memleaks"]
+ assert topotest.g_pytest_config.option.valgrind_memleaks
leaks = []
tgen = get_topogen() # pylint: disable=redefined-outer-name
@@ -177,16 +234,15 @@ def check_for_memleaks():
@pytest.fixture(autouse=True, scope="module")
def module_check_memtest(request):
- del request # disable unused warning
yield
- if topotest_extra_config["valgrind_memleaks"]:
+ if request.config.option.valgrind_memleaks:
if get_topogen() is not None:
check_for_memleaks()
def pytest_runtest_logstart(nodeid, location):
# location is (filename, lineno, testname)
- topolog.logstart(nodeid, location, topotest_extra_config["rundir"])
+ topolog.logstart(nodeid, location, topotest.g_pytest_config.option.rundir)
def pytest_runtest_logfinish(nodeid, location):
@@ -197,10 +253,9 @@ def pytest_runtest_logfinish(nodeid, location):
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item: pytest.Item) -> None:
"Hook the function that is called to execute the test."
- del item # disable unused warning
# For topology only run the CLI then exit
- if topotest_extra_config["topology_only"]:
+ if item.config.option.topology_only:
get_topogen().cli()
pytest.exit("exiting after --topology-only")
@@ -208,7 +263,7 @@ def pytest_runtest_call(item: pytest.Item) -> None:
yield
# Check for leaks if requested
- if topotest_extra_config["valgrind_memleaks"]:
+ if item.config.option.valgrind_memleaks:
check_for_memleaks()
@@ -231,6 +286,7 @@ def pytest_configure(config):
"""
Assert that the environment is correctly configured, and get extra config.
"""
+ topotest.g_pytest_config = ConfigOptionsProxy(config)
if config.getoption("--collect-only"):
return
@@ -252,11 +308,13 @@ def pytest_configure(config):
# Set some defaults for the pytest.ini [pytest] section
# ---------------------------------------------------
- rundir = config.getoption("--rundir")
+ rundir = config.option.rundir
if not rundir:
rundir = config.getini("rundir")
if not rundir:
rundir = "/tmp/topotests"
+ config.option.rundir = rundir
+
if not config.getoption("--junitxml"):
config.option.xmlpath = os.path.join(rundir, "topotests.xml")
xmlpath = config.option.xmlpath
@@ -269,8 +327,6 @@ def pytest_configure(config):
mv_path = commander.get_exec_path("mv")
commander.cmd_status([mv_path, xmlpath, xmlpath + suffix])
- topotest_extra_config["rundir"] = rundir
-
# Set the log_file (exec) to inside the rundir if not specified
if not config.getoption("--log-file") and not config.getini("log_file"):
config.option.log_file = os.path.join(rundir, "exec.log")
@@ -300,69 +356,19 @@ def pytest_configure(config):
elif b and not is_xdist and not have_windows:
pytest.exit("{} use requires byobu/TMUX/SCREEN/XTerm".format(feature))
- # ---------------------------------------
- # Record our options in global dictionary
- # ---------------------------------------
-
- topotest_extra_config["rundir"] = rundir
-
- asan_abort = config.getoption("--asan-abort")
- topotest_extra_config["asan_abort"] = asan_abort
-
- gdb_routers = config.getoption("--gdb-routers")
- gdb_routers = gdb_routers.split(",") if gdb_routers else []
- topotest_extra_config["gdb_routers"] = gdb_routers
-
- gdb_daemons = config.getoption("--gdb-daemons")
- gdb_daemons = gdb_daemons.split(",") if gdb_daemons else []
- topotest_extra_config["gdb_daemons"] = gdb_daemons
- assert_feature_windows(gdb_routers or gdb_daemons, "GDB")
-
- gdb_breakpoints = config.getoption("--gdb-breakpoints")
- gdb_breakpoints = gdb_breakpoints.split(",") if gdb_breakpoints else []
- topotest_extra_config["gdb_breakpoints"] = gdb_breakpoints
-
- cli_on_error = config.getoption("--cli-on-error")
- topotest_extra_config["cli_on_error"] = cli_on_error
- assert_feature_windows(cli_on_error, "--cli-on-error")
-
- shell = config.getoption("--shell")
- topotest_extra_config["shell"] = shell.split(",") if shell else []
- assert_feature_windows(shell, "--shell")
-
- strace = config.getoption("--strace-daemons")
- topotest_extra_config["strace_daemons"] = strace.split(",") if strace else []
-
- shell_on_error = config.getoption("--shell-on-error")
- topotest_extra_config["shell_on_error"] = shell_on_error
- assert_feature_windows(shell_on_error, "--shell-on-error")
-
- topotest_extra_config["valgrind_extra"] = config.getoption("--valgrind-extra")
- topotest_extra_config["valgrind_memleaks"] = config.getoption("--valgrind-memleaks")
-
- vtysh = config.getoption("--vtysh")
- topotest_extra_config["vtysh"] = vtysh.split(",") if vtysh else []
- assert_feature_windows(vtysh, "--vtysh")
-
- vtysh_on_error = config.getoption("--vtysh-on-error")
- topotest_extra_config["vtysh_on_error"] = vtysh_on_error
- assert_feature_windows(vtysh_on_error, "--vtysh-on-error")
-
- pause_on_error = vtysh or shell or config.getoption("--pause-on-error")
- if config.getoption("--no-pause-on-error"):
- pause_on_error = False
-
- topotest_extra_config["pause_on_error"] = pause_on_error
- assert_feature_windows(pause_on_error, "--pause-on-error")
-
- pause = config.getoption("--pause")
- topotest_extra_config["pause"] = pause
- assert_feature_windows(pause, "--pause")
-
- topology_only = config.getoption("--topology-only")
- if topology_only and is_xdist:
+ #
+ # Check for window capability if given options that require window
+ #
+ assert_feature_windows(config.option.gdb_routers, "GDB")
+ assert_feature_windows(config.option.gdb_daemons, "GDB")
+ assert_feature_windows(config.option.cli_on_error, "--cli-on-error")
+ assert_feature_windows(config.option.shell, "--shell")
+ assert_feature_windows(config.option.shell_on_error, "--shell-on-error")
+ assert_feature_windows(config.option.vtysh, "--vtysh")
+ assert_feature_windows(config.option.vtysh_on_error, "--vtysh-on-error")
+
+ if config.option.topology_only and is_xdist:
pytest.exit("Cannot use --topology-only with distributed test mode")
- topotest_extra_config["topology_only"] = topology_only
# Check environment now that we have config
if not diagnose_env(rundir):
@@ -428,10 +434,7 @@ def pytest_runtest_makereport(item, call):
# We want to pause, if requested, on any error not just test cases
# (e.g., call.when == "setup")
if not pause:
- pause = (
- topotest_extra_config["pause_on_error"]
- or topotest_extra_config["pause"]
- )
+ pause = item.config.option.pause_on_error or item.config.option.pause
# (topogen) Set topology error to avoid advancing in the test.
tgen = get_topogen() # pylint: disable=redefined-outer-name
@@ -443,9 +446,9 @@ def pytest_runtest_makereport(item, call):
isatty = sys.stdout.isatty()
error_cmd = None
- if error and topotest_extra_config["vtysh_on_error"]:
+ if error and item.config.option.vtysh_on_error:
error_cmd = commander.get_exec_path(["vtysh"])
- elif error and topotest_extra_config["shell_on_error"]:
+ elif error and item.config.option.shell_on_error:
error_cmd = os.getenv("SHELL", commander.get_exec_path(["bash"]))
if error_cmd:
@@ -497,31 +500,16 @@ def pytest_runtest_makereport(item, call):
if p.wait():
logger.warning("xterm proc failed: %s:", proc_error(p, o, e))
- if error and topotest_extra_config["cli_on_error"]:
+ if error and item.config.option.cli_on_error:
# Really would like something better than using this global here.
# Not all tests use topogen though so get_topogen() won't work.
if Mininet.g_mnet_inst:
- cli(Mininet.g_mnet_inst, title=title, background=False)
+ cli.cli(Mininet.g_mnet_inst, title=title, background=False)
else:
logger.error("Could not launch CLI b/c no mininet exists yet")
- while pause and isatty:
- try:
- user = raw_input(
- 'PAUSED, "cli" for CLI, "pdb" to debug, "Enter" to continue: '
- )
- except NameError:
- user = input('PAUSED, "cli" for CLI, "pdb" to debug, "Enter" to continue: ')
- user = user.strip()
-
- if user == "cli":
- cli(Mininet.g_mnet_inst)
- elif user == "pdb":
- pdb.set_trace() # pylint: disable=forgotten-debug-statement
- elif user:
- print('Unrecognized input: "%s"' % user)
- else:
- break
+ if pause and isatty:
+ pause_test()
#
diff --git a/tests/topotests/cspf_topo1/reference/sharp-ted.json b/tests/topotests/cspf_topo1/reference/sharp-ted.json
index da240e87a3..359b655f01 100644
--- a/tests/topotests/cspf_topo1/reference/sharp-ted.json
+++ b/tests/topotests/cspf_topo1/reference/sharp-ted.json
@@ -53,7 +53,7 @@
],
"edges":[
{
- "edge-id":65537,
+ "edge-id":"2001:db8:1::1:1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -96,7 +96,7 @@
}
},
{
- "edge-id":65538,
+ "edge-id":"2001:db8:1::1:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -139,7 +139,7 @@
}
},
{
- "edge-id":196610,
+ "edge-id":"2001:db8:3::3:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -182,7 +182,7 @@
}
},
{
- "edge-id":196611,
+ "edge-id":"2001:db8:3::3:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -226,7 +226,7 @@
}
},
{
- "edge-id":196612,
+ "edge-id":"2001:db8:5::3:4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
@@ -275,7 +275,7 @@
]
},
{
- "edge-id":262147,
+ "edge-id":"2001:db8:5::4:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -318,7 +318,7 @@
}
},
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -362,7 +362,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -405,7 +405,7 @@
}
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -448,7 +448,7 @@
}
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -491,7 +491,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -534,7 +534,7 @@
}
},
{
- "edge-id":167772931,
+ "edge-id":"10.0.3.3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -578,7 +578,7 @@
}
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -622,7 +622,7 @@
}
},
{
- "edge-id":167773188,
+ "edge-id":"10.0.4.4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
diff --git a/tests/topotests/example_munet/munet.yaml b/tests/topotests/example_munet/munet.yaml
new file mode 100644
index 0000000000..34e1470103
--- /dev/null
+++ b/tests/topotests/example_munet/munet.yaml
@@ -0,0 +1,17 @@
+version: 1
+topology:
+ ipv6-enable: true
+ networks-autonumber: true
+ networks:
+ - name: net1
+ - name: net2
+ nodes:
+ - name: r1
+ kind: frr
+ connections: ["net1"]
+ - name: r2
+ kind: frr
+ connections: ["net1", "net2"]
+ - name: r3
+ kind: frr
+ connections: ["net2"]
diff --git a/tests/topotests/example_munet/r1/daemons b/tests/topotests/example_munet/r1/daemons
new file mode 100644
index 0000000000..a454c95923
--- /dev/null
+++ b/tests/topotests/example_munet/r1/daemons
@@ -0,0 +1,6 @@
+zebra=1
+staticd=1
+vtysh_enable=1
+watchfrr_enable=1
+zebra_options="-d -F traditional --log=file:/var/log/frr/zebra.log"
+staticd_options="-d -F traditional --log=file:/var/log/frr/staticd.log"
diff --git a/tests/topotests/example_munet/r1/frr.conf b/tests/topotests/example_munet/r1/frr.conf
new file mode 100644
index 0000000000..468bda5e01
--- /dev/null
+++ b/tests/topotests/example_munet/r1/frr.conf
@@ -0,0 +1,7 @@
+log file /var/log/frr/frr.log
+service integrated-vtysh-config
+
+interface eth0
+ ip address 10.0.1.1/24
+
+ip route 10.0.0.0/8 blackhole
diff --git a/tests/topotests/example_munet/r1/vtysh.conf b/tests/topotests/example_munet/r1/vtysh.conf
new file mode 100644
index 0000000000..f863f560f1
--- /dev/null
+++ b/tests/topotests/example_munet/r1/vtysh.conf
@@ -0,0 +1 @@
+service integrated-vtysh-config \ No newline at end of file
diff --git a/tests/topotests/example_munet/r2/daemons b/tests/topotests/example_munet/r2/daemons
new file mode 100644
index 0000000000..a454c95923
--- /dev/null
+++ b/tests/topotests/example_munet/r2/daemons
@@ -0,0 +1,6 @@
+zebra=1
+staticd=1
+vtysh_enable=1
+watchfrr_enable=1
+zebra_options="-d -F traditional --log=file:/var/log/frr/zebra.log"
+staticd_options="-d -F traditional --log=file:/var/log/frr/staticd.log"
diff --git a/tests/topotests/example_munet/r2/frr.conf b/tests/topotests/example_munet/r2/frr.conf
new file mode 100644
index 0000000000..77d9892485
--- /dev/null
+++ b/tests/topotests/example_munet/r2/frr.conf
@@ -0,0 +1,10 @@
+log file /var/log/frr/frr.log
+service integrated-vtysh-config
+
+interface eth0
+ ip address 10.0.1.2/24
+
+interface eth1
+ ip address 10.0.2.2/24
+
+ip route 10.0.0.0/8 blackhole
diff --git a/tests/topotests/example_munet/r2/vtysh.conf b/tests/topotests/example_munet/r2/vtysh.conf
new file mode 100644
index 0000000000..f863f560f1
--- /dev/null
+++ b/tests/topotests/example_munet/r2/vtysh.conf
@@ -0,0 +1 @@
+service integrated-vtysh-config \ No newline at end of file
diff --git a/tests/topotests/example_munet/r3/daemons b/tests/topotests/example_munet/r3/daemons
new file mode 100644
index 0000000000..a454c95923
--- /dev/null
+++ b/tests/topotests/example_munet/r3/daemons
@@ -0,0 +1,6 @@
+zebra=1
+staticd=1
+vtysh_enable=1
+watchfrr_enable=1
+zebra_options="-d -F traditional --log=file:/var/log/frr/zebra.log"
+staticd_options="-d -F traditional --log=file:/var/log/frr/staticd.log"
diff --git a/tests/topotests/example_munet/r3/frr.conf b/tests/topotests/example_munet/r3/frr.conf
new file mode 100644
index 0000000000..e0839e6d8a
--- /dev/null
+++ b/tests/topotests/example_munet/r3/frr.conf
@@ -0,0 +1,7 @@
+log file /var/log/frr/frr.log
+service integrated-vtysh-config
+
+interface eth0
+ ip address 10.0.2.3/24
+
+ip route 10.0.0.0/8 blackhole
diff --git a/tests/topotests/example_munet/r3/vtysh.conf b/tests/topotests/example_munet/r3/vtysh.conf
new file mode 100644
index 0000000000..f863f560f1
--- /dev/null
+++ b/tests/topotests/example_munet/r3/vtysh.conf
@@ -0,0 +1 @@
+service integrated-vtysh-config \ No newline at end of file
diff --git a/tests/topotests/example_munet/test_munet.py b/tests/topotests/example_munet/test_munet.py
new file mode 100644
index 0000000000..0d9599fa54
--- /dev/null
+++ b/tests/topotests/example_munet/test_munet.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# April 23 2023, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2023, LabN Consulting, L.L.C.
+#
+async def test_native_test(unet):
+ o = unet.hosts["r1"].cmd_nostatus("ip addr")
+ print(o)
diff --git a/tests/topotests/isis_advertise_high_metrics/test_isis_advertise_high_metrics.py b/tests/topotests/isis_advertise_high_metrics/test_isis_advertise_high_metrics.py
index 5eef879e3f..ada8c0f5fb 100644
--- a/tests/topotests/isis_advertise_high_metrics/test_isis_advertise_high_metrics.py
+++ b/tests/topotests/isis_advertise_high_metrics/test_isis_advertise_high_metrics.py
@@ -121,14 +121,14 @@ def _check_interface_metrics(router, expected_metrics):
tgen = get_topogen()
router = tgen.gears[router]
logger.info(f"check_interface_metrics {router}")
- isis_interface_output = router.vtysh_cmd(
- "show isis interface detail json"
- )
+ isis_interface_output = router.vtysh_cmd("show isis interface detail json")
intf_json = json.loads(isis_interface_output)
for i in range(len(expected_metrics)):
- metric = intf_json["areas"][0]["circuits"][i]["interface"]["levels"][0]["metric"]
- if (metric != expected_metrics[i]):
+ metric = intf_json["areas"][0]["circuits"][i]["interface"]["levels"][0][
+ "metric"
+ ]
+ if metric != expected_metrics[i]:
intf_name = intf_json["areas"][0]["circuits"][i]["interface"]["name"]
return "{} with expected metric {} on {} got {}".format(
router.name, expected_metrics[i], intf_name, metric
@@ -139,9 +139,7 @@ def _check_interface_metrics(router, expected_metrics):
def check_interface_metrics(router, expected_metrics):
"Verfiy metrics on router's isis interfaces"
- assertmsg = _check_interface_metrics(
- router, expected_metrics
- )
+ assertmsg = _check_interface_metrics(router, expected_metrics)
assert assertmsg is True, assertmsg
@@ -151,9 +149,7 @@ def _check_lsp_metrics(router, lsp, expected_metrics):
tgen = get_topogen()
router = tgen.gears[router]
logger.info(f"check_lsp_metrics {router}")
- isis_lsp_output = router.vtysh_cmd(
- "show isis database detail {}".format(lsp)
- )
+ isis_lsp_output = router.vtysh_cmd("show isis database detail {}".format(lsp))
metrics_list = [int(i) for i in re.findall(r"Metric: (\d+)", isis_lsp_output)]
if len(metrics_list) == 0:
@@ -170,9 +166,7 @@ def _check_lsp_metrics(router, lsp, expected_metrics):
def check_lsp_metrics(router, lsp, expected_metrics):
"Verfiy metrics on router's lsp"
- assertmsg = _check_lsp_metrics(
- router, lsp, expected_metrics
- )
+ assertmsg = _check_lsp_metrics(router, lsp, expected_metrics)
assert assertmsg is True, assertmsg
@@ -183,14 +177,12 @@ def _check_ip_route(router, destination, expected_interface):
tgen = get_topogen()
router = tgen.gears[router]
logger.info(f"check_ip_route {router}")
- route_output = router.vtysh_cmd(
- "show ip route {} json".format(destination)
- )
+ route_output = router.vtysh_cmd("show ip route {} json".format(destination))
route_json = json.loads(route_output)
interface = route_json[destination][0]["nexthops"][0]["interfaceName"]
- if (interface != expected_interface):
+ if interface != expected_interface:
return "{} with expected route to {} got {} expected {}".format(
router.name, destination, interface, expected_interface
)
@@ -201,9 +193,7 @@ def _check_ip_route(router, destination, expected_interface):
def check_ip_route(router, destination, expected_interface):
"Verfiy IS-IS route"
- assertmsg = _check_ip_route(
- router, destination, expected_interface
- )
+ assertmsg = _check_ip_route(router, destination, expected_interface)
assert assertmsg is True, assertmsg
@@ -216,9 +206,7 @@ def test_isis_daemon_up():
for router in ["r1", "r2", "r3", "r4"]:
r = tgen.gears[router]
- daemons = r.vtysh_cmd(
- "show daemons"
- )
+ daemons = r.vtysh_cmd("show daemons")
assert "isisd" in daemons
# Verify initial metric values.
@@ -420,9 +408,9 @@ def test_isis_advertise_high_metrics_route():
Topology:
r2
- / \
+ // \\
r1 r4
- \ /
+ \\ //
r3
Devices are configured with preferred route between r1 and r4:
diff --git a/tests/topotests/isis_snmp/r5/ldpdconf b/tests/topotests/isis_snmp/r5/ldpdconf
index fc700608b5..b3d10b07ec 100644
--- a/tests/topotests/isis_snmp/r5/ldpdconf
+++ b/tests/topotests/isis_snmp/r5/ldpdconf
@@ -1,10 +1,10 @@
hostname r5
log file ldpd.log
!
-debug mpls ldp zebra
-debug mpls ldp event
-debug mpls ldp errors
-debug mpls ldp sync
+!debug mpls ldp zebra
+!debug mpls ldp event
+!debug mpls ldp errors
+!debug mpls ldp sync
!
mpls ldp
router-id 3.3.3.3
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo1/rt1/isisd.conf
new file mode 100644
index 0000000000..5503baa58c
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/isisd.conf
@@ -0,0 +1,96 @@
+password 1
+hostname rt1
+log file isisd.log
+!
+!debug northbound
+!debug isis events
+!debug isis spf-events
+!debug isis route-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map red bit-position 0
+affinity-map blue bit-position 1
+affinity-map green bit-position 2
+affinity-map yellow bit-position 3
+affinity-map orange bit-position 4
+!
+interface lo
+ ip router isis 1
+ ipv6 router isis 1
+ isis passive
+!
+interface eth-rt2
+ ip router isis 1
+ ipv6 router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+!
+interface eth-rt3
+ ip router isis 1
+ ipv6 router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.0001.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 201
+ dataplane sr-mpls
+ advertise-definition
+ affinity exclude-any red
+ !
+ flex-algo 202
+ dataplane sr-mpls
+ advertise-definition
+ affinity exclude-any blue
+ !
+ flex-algo 203
+ dataplane sr-mpls
+ advertise-definition
+ affinity exclude-any green
+ !
+ flex-algo 204
+ dataplane sr-mpls
+ advertise-definition
+ affinity include-any blue green
+ !
+ flex-algo 205
+ dataplane sr-mpls
+ advertise-definition
+ affinity include-any red green
+ !
+ flex-algo 206
+ dataplane sr-mpls
+ advertise-definition
+ affinity include-any red blue
+ !
+ flex-algo 207
+ dataplane sr-mpls
+ advertise-definition
+ affinity include-all yellow orange
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 1.1.1.1/32 index 1
+ segment-routing prefix 1.1.1.1/32 algorithm 201 index 101
+ segment-routing prefix 1.1.1.1/32 algorithm 202 index 201
+ segment-routing prefix 1.1.1.1/32 algorithm 203 index 301
+ segment-routing prefix 1.1.1.1/32 algorithm 204 index 401
+ segment-routing prefix 1.1.1.1/32 algorithm 205 index 501
+ segment-routing prefix 1.1.1.1/32 algorithm 206 index 601
+ segment-routing prefix 1.1.1.1/32 algorithm 207 index 701
+ segment-routing prefix 2001:db8:1000::1/128 index 1001
+ segment-routing prefix 2001:db8:1000::1/128 algorithm 201 index 1101
+ segment-routing prefix 2001:db8:1000::1/128 algorithm 202 index 1201
+ segment-routing prefix 2001:db8:1000::1/128 algorithm 203 index 1301
+ segment-routing prefix 2001:db8:1000::1/128 algorithm 204 index 1401
+ segment-routing prefix 2001:db8:1000::1/128 algorithm 205 index 1501
+ segment-routing prefix 2001:db8:1000::1/128 algorithm 206 index 1601
+ segment-routing prefix 2001:db8:1000::1/128 algorithm 207 index 1701
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step1/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step1/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..750abc1fa3
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step1/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set \ No newline at end of file
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step1/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step1/show_mpls_table.ref
new file mode 100644
index 0000000000..2b16c53132
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step1/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21303,
+ "outLabelStack":[
+ 21303
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step10/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step10/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..9421990316
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step10/show_isis_flex_algo.ref
@@ -0,0 +1,126 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
+
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step10/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step10/show_mpls_table.ref
new file mode 100644
index 0000000000..2b16c53132
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step10/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21303,
+ "outLabelStack":[
+ 21303
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step11/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step11/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..9421990316
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step11/show_isis_flex_algo.ref
@@ -0,0 +1,126 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
+
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step11/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step11/show_mpls_table.ref
new file mode 100644
index 0000000000..2b16c53132
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step11/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21303,
+ "outLabelStack":[
+ 21303
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step2/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step2/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..9421990316
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step2/show_isis_flex_algo.ref
@@ -0,0 +1,126 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
+
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step2/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step2/show_mpls_table.ref
new file mode 100644
index 0000000000..2b16c53132
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step2/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21303,
+ "outLabelStack":[
+ 21303
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step3/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step3/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..13a96161af
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step3/show_isis_flex_algo.ref
@@ -0,0 +1,115 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: Not found
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
+
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step3/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step3/show_mpls_table.ref
new file mode 100644
index 0000000000..d311e924d6
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step3/show_mpls_table.ref
@@ -0,0 +1,450 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step4/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step4/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..9421990316
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step4/show_isis_flex_algo.ref
@@ -0,0 +1,126 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
+
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step4/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step4/show_mpls_table.ref
new file mode 100644
index 0000000000..2b16c53132
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step4/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21303,
+ "outLabelStack":[
+ 21303
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step5/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step5/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..9421990316
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step5/show_isis_flex_algo.ref
@@ -0,0 +1,126 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
+
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step5/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step5/show_mpls_table.ref
new file mode 100644
index 0000000000..2b16c53132
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step5/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21303,
+ "outLabelStack":[
+ 21303
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step6/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step6/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..ce31766da5
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step6/show_isis_flex_algo.ref
@@ -0,0 +1,112 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: None
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
+
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step6/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step6/show_mpls_table.ref
new file mode 100644
index 0000000000..d311e924d6
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step6/show_mpls_table.ref
@@ -0,0 +1,450 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step7/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step7/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..e56e5af2f9
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step7/show_isis_flex_algo.ref
@@ -0,0 +1,108 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
+
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step7/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step7/show_mpls_table.ref
new file mode 100644
index 0000000000..d311e924d6
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step7/show_mpls_table.ref
@@ -0,0 +1,450 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step8/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step8/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..9421990316
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step8/show_isis_flex_algo.ref
@@ -0,0 +1,126 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
+
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step8/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step8/show_mpls_table.ref
new file mode 100644
index 0000000000..2b16c53132
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step8/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21303,
+ "outLabelStack":[
+ 21303
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step9/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step9/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..9421990316
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step9/show_isis_flex_algo.ref
@@ -0,0 +1,126 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
+
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/step9/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step9/show_mpls_table.ref
new file mode 100644
index 0000000000..2b16c53132
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/step9/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20402,
+ "outLabelStack":[
+ 20402
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20603,
+ "outLabelStack":[
+ 20603
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20702,
+ "outLabelStack":[
+ 20702
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.3"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21102,
+ "outLabelStack":[
+ 21102
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21303,
+ "outLabelStack":[
+ 21303
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21402,
+ "outLabelStack":[
+ 21402
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21603,
+ "outLabelStack":[
+ 21603
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21702,
+ "outLabelStack":[
+ 21702
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt1/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo1/rt1/zebra.conf
new file mode 100644
index 0000000000..5140eda73a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt1/zebra.conf
@@ -0,0 +1,39 @@
+log file zebra.log
+!
+hostname rt1
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map red bit-position 0
+affinity-map blue bit-position 1
+affinity-map green bit-position 2
+affinity-map yellow bit-position 3
+affinity-map orange bit-position 4
+!
+interface lo
+ ip address 1.1.1.1/32
+ ipv6 address 2001:db8:1000::1/128
+!
+interface eth-rt2
+ ip address 10.12.0.1/24
+ link-params
+ affinity red
+ exit-link-params
+!
+interface eth-rt3
+ ip address 10.13.0.1/24
+ link-params
+ affinity green yellow orange
+ exit-link-params
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo1/rt2/isisd.conf
new file mode 100644
index 0000000000..8655e7434a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/isisd.conf
@@ -0,0 +1,96 @@
+password 1
+hostname rt2
+log file isisd.log
+!
+!debug northbound
+!debug isis events
+!debug isis route-events
+!debug isis spf-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map red bit-position 0
+affinity-map blue bit-position 1
+affinity-map green bit-position 2
+affinity-map yellow bit-position 3
+affinity-map orange bit-position 4
+!
+interface lo
+ ip router isis 1
+ ipv6 router isis 1
+ isis passive
+!
+interface eth-rt1
+ ip router isis 1
+ ipv6 router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+!
+interface eth-rt3
+ ip router isis 1
+ ipv6 router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.0002.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 201
+ dataplane sr-mpls
+ advertise-definition
+ affinity exclude-any red
+ !
+ flex-algo 202
+ dataplane sr-mpls
+ advertise-definition
+ affinity exclude-any blue
+ !
+ flex-algo 203
+ dataplane sr-mpls
+ advertise-definition
+ affinity exclude-any green
+ !
+ flex-algo 204
+ dataplane sr-mpls
+ advertise-definition
+ affinity include-any blue green
+ !
+ flex-algo 205
+ dataplane sr-mpls
+ advertise-definition
+ affinity include-any red green
+ !
+ flex-algo 206
+ dataplane sr-mpls
+ advertise-definition
+ affinity include-any red blue
+ !
+ flex-algo 207
+ dataplane sr-mpls
+ advertise-definition
+ affinity include-all yellow orange
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 2.2.2.2/32 index 2
+ segment-routing prefix 2.2.2.2/32 algorithm 201 index 102
+ segment-routing prefix 2.2.2.2/32 algorithm 202 index 202
+ segment-routing prefix 2.2.2.2/32 algorithm 203 index 302
+ segment-routing prefix 2.2.2.2/32 algorithm 204 index 402
+ segment-routing prefix 2.2.2.2/32 algorithm 205 index 502
+ segment-routing prefix 2.2.2.2/32 algorithm 206 index 602
+ segment-routing prefix 2.2.2.2/32 algorithm 207 index 702
+ segment-routing prefix 2001:db8:1000::2/128 index 1002
+ segment-routing prefix 2001:db8:1000::2/128 algorithm 201 index 1102
+ segment-routing prefix 2001:db8:1000::2/128 algorithm 202 index 1202
+ segment-routing prefix 2001:db8:1000::2/128 algorithm 203 index 1302
+ segment-routing prefix 2001:db8:1000::2/128 algorithm 204 index 1402
+ segment-routing prefix 2001:db8:1000::2/128 algorithm 205 index 1502
+ segment-routing prefix 2001:db8:1000::2/128 algorithm 206 index 1602
+ segment-routing prefix 2001:db8:1000::2/128 algorithm 207 index 1702
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step1/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step1/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..defa3efa6b
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step1/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step1/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step1/show_mpls_table.ref
new file mode 100644
index 0000000000..099045a14d
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step1/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step10/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step10/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..defa3efa6b
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step10/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step10/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step10/show_mpls_table.ref
new file mode 100644
index 0000000000..099045a14d
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step10/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step11/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step11/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..defa3efa6b
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step11/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step11/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step11/show_mpls_table.ref
new file mode 100644
index 0000000000..0ed0eb31ee
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step11/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20311":{
+ "inLabel":20311,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21311":{
+ "inLabel":21311,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step2/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step2/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..defa3efa6b
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step2/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step2/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step2/show_mpls_table.ref
new file mode 100644
index 0000000000..099045a14d
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step2/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step3/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step3/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..b10cb70866
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step3/show_isis_flex_algo.ref
@@ -0,0 +1,114 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: Not found
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step3/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step3/show_mpls_table.ref
new file mode 100644
index 0000000000..8b407387b4
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step3/show_mpls_table.ref
@@ -0,0 +1,450 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step4/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step4/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..defa3efa6b
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step4/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step4/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step4/show_mpls_table.ref
new file mode 100644
index 0000000000..099045a14d
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step4/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step5/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step5/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..defa3efa6b
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step5/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step5/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step5/show_mpls_table.ref
new file mode 100644
index 0000000000..099045a14d
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step5/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step6/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step6/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..358c4310ed
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step6/show_isis_flex_algo.ref
@@ -0,0 +1,111 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: None
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step6/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step6/show_mpls_table.ref
new file mode 100644
index 0000000000..8b407387b4
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step6/show_mpls_table.ref
@@ -0,0 +1,450 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step7/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step7/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..04d07e6414
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step7/show_isis_flex_algo.ref
@@ -0,0 +1,107 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step7/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step7/show_mpls_table.ref
new file mode 100644
index 0000000000..8b407387b4
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step7/show_mpls_table.ref
@@ -0,0 +1,450 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step8/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step8/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..defa3efa6b
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step8/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step8/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step8/show_mpls_table.ref
new file mode 100644
index 0000000000..099045a14d
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step8/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step9/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step9/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..defa3efa6b
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step9/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: yes
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/step9/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step9/show_mpls_table.ref
new file mode 100644
index 0000000000..3db0ebfbf6
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/step9/show_mpls_table.ref
@@ -0,0 +1,482 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20203":{
+ "inLabel":20203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20203,
+ "outLabelStack":[
+ 20203
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20401,
+ "outLabelStack":[
+ 20401
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20403":{
+ "inLabel":20403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20503":{
+ "inLabel":20503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20503,
+ "outLabelStack":[
+ 20503
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20603":{
+ "inLabel":20603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20701,
+ "outLabelStack":[
+ 20701
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20703":{
+ "inLabel":20703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21003":{
+ "inLabel":21003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21101,
+ "outLabelStack":[
+ 21101
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21103":{
+ "inLabel":21103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21203":{
+ "inLabel":21203,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21203,
+ "outLabelStack":[
+ 21203
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21303":{
+ "inLabel":21303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21401,
+ "outLabelStack":[
+ 21401
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21403":{
+ "inLabel":21403,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21503":{
+ "inLabel":21503,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21503,
+ "outLabelStack":[
+ 21503
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21603":{
+ "inLabel":21603,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21701,
+ "outLabelStack":[
+ 21701
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ },
+ "21703":{
+ "inLabel":21703,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt2/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo1/rt2/zebra.conf
new file mode 100644
index 0000000000..388348fced
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt2/zebra.conf
@@ -0,0 +1,39 @@
+log file zebra.log
+!
+hostname rt2
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map red bit-position 0
+affinity-map blue bit-position 1
+affinity-map green bit-position 2
+affinity-map yellow bit-position 3
+affinity-map orange bit-position 4
+!
+interface lo
+ ip address 2.2.2.2/32
+ ipv6 address 2001:db8:1000::2/128
+!
+interface eth-rt1
+ ip address 10.12.0.2/24
+ link-params
+ affinity red
+ exit-link-params
+!
+interface eth-rt3
+ ip address 10.23.0.2/24
+ link-params
+ affinity blue yellow orange
+ exit-link-params
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo1/rt3/isisd.conf
new file mode 100644
index 0000000000..d77af81d7c
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/isisd.conf
@@ -0,0 +1,76 @@
+password 1
+hostname rt3
+log file isisd.log
+!
+!debug northbound
+!debug isis events
+!debug isis route-events
+!debug isis spf-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map red bit-position 0
+affinity-map blue bit-position 1
+affinity-map green bit-position 2
+affinity-map yellow bit-position 3
+affinity-map orange bit-position 4
+!
+interface lo
+ ip router isis 1
+ ipv6 router isis 1
+ isis passive
+!
+interface eth-rt1
+ ip router isis 1
+ ipv6 router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+!
+interface eth-rt2
+ ip router isis 1
+ ipv6 router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.0003.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 201
+ dataplane sr-mpls
+ flex-algo 202
+ dataplane sr-mpls
+ flex-algo 203
+ dataplane sr-mpls
+ flex-algo 204
+ dataplane sr-mpls
+ flex-algo 205
+ dataplane sr-mpls
+ flex-algo 206
+ dataplane sr-mpls
+ flex-algo 207
+ dataplane sr-mpls
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 3.3.3.3/32 index 3
+ segment-routing prefix 3.3.3.3/32 algorithm 201 index 103
+ segment-routing prefix 3.3.3.3/32 algorithm 202 index 203
+ segment-routing prefix 3.3.3.3/32 algorithm 203 index 303
+ segment-routing prefix 3.3.3.3/32 algorithm 204 index 403
+ segment-routing prefix 3.3.3.3/32 algorithm 205 index 503
+ segment-routing prefix 3.3.3.3/32 algorithm 206 index 603
+ segment-routing prefix 3.3.3.3/32 algorithm 207 index 703
+ segment-routing prefix 2001:db8:1000::3/128 index 1003
+ segment-routing prefix 2001:db8:1000::3/128 algorithm 201 index 1103
+ segment-routing prefix 2001:db8:1000::3/128 algorithm 202 index 1203
+ segment-routing prefix 2001:db8:1000::3/128 algorithm 203 index 1303
+ segment-routing prefix 2001:db8:1000::3/128 algorithm 204 index 1403
+ segment-routing prefix 2001:db8:1000::3/128 algorithm 205 index 1503
+ segment-routing prefix 2001:db8:1000::3/128 algorithm 206 index 1603
+ segment-routing prefix 2001:db8:1000::3/128 algorithm 207 index 1703
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step1/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step1/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..7954734936
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step1/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set \ No newline at end of file
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step1/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step1/show_mpls_table.ref
new file mode 100644
index 0000000000..9ab0c74d0a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step1/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21301,
+ "outLabelStack":[
+ 21301
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step10/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step10/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..85182db33a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step10/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step10/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step10/show_mpls_table.ref
new file mode 100644
index 0000000000..9ab0c74d0a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step10/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21301,
+ "outLabelStack":[
+ 21301
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step11/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step11/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..85182db33a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step11/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step11/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step11/show_mpls_table.ref
new file mode 100644
index 0000000000..57755b00e6
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step11/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20311":{
+ "inLabel":20311,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20311,
+ "outLabelStack":[
+ 20311
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21311":{
+ "inLabel":21311,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21311,
+ "outLabelStack":[
+ 21311
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step2/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step2/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..85182db33a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step2/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step2/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step2/show_mpls_table.ref
new file mode 100644
index 0000000000..9ab0c74d0a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step2/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21301,
+ "outLabelStack":[
+ 21301
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step3/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step3/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..2ccc4f1655
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step3/show_isis_flex_algo.ref
@@ -0,0 +1,114 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: Not found
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step3/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step3/show_mpls_table.ref
new file mode 100644
index 0000000000..1b57f575b2
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step3/show_mpls_table.ref
@@ -0,0 +1,450 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step4/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step4/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..85182db33a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step4/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step4/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step4/show_mpls_table.ref
new file mode 100644
index 0000000000..9ab0c74d0a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step4/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21301,
+ "outLabelStack":[
+ 21301
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step5/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step5/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..85182db33a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step5/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step5/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step5/show_mpls_table.ref
new file mode 100644
index 0000000000..9ab0c74d0a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step5/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21301,
+ "outLabelStack":[
+ 21301
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step6/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step6/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..903c0f2bed
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step6/show_isis_flex_algo.ref
@@ -0,0 +1,111 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: None
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step6/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step6/show_mpls_table.ref
new file mode 100644
index 0000000000..1b57f575b2
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step6/show_mpls_table.ref
@@ -0,0 +1,450 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step7/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step7/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..f36d96579d
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step7/show_isis_flex_algo.ref
@@ -0,0 +1,107 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step7/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step7/show_mpls_table.ref
new file mode 100644
index 0000000000..1b57f575b2
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step7/show_mpls_table.ref
@@ -0,0 +1,450 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step8/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step8/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..85182db33a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step8/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step8/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step8/show_mpls_table.ref
new file mode 100644
index 0000000000..9ab0c74d0a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step8/show_mpls_table.ref
@@ -0,0 +1,514 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21301":{
+ "inLabel":21301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21301,
+ "outLabelStack":[
+ 21301
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step9/show_isis_flex_algo.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step9/show_isis_flex_algo.ref
new file mode 100644
index 0000000000..85182db33a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step9/show_isis_flex_algo.ref
@@ -0,0 +1,125 @@
+Area 1: Algorithm 201
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000001
+ Bit positions: 0
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 202
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000002
+ Bit positions: 1
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 203
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: 0x00000004
+ Bit positions: 2
+ Include-all admin-group: not-set
+ Include-any admin-group: not-set
+
+Area 1: Algorithm 204
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000006
+ Bit positions: 1, 2
+
+Area 1: Algorithm 205
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000005
+ Bit positions: 0, 2
+
+Area 1: Algorithm 206
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: not-set
+ Include-any admin-group: 0x00000003
+ Bit positions: 0, 1
+
+Area 1: Algorithm 207
+
+ Enabled Data-Planes: SR-MPLS
+
+ Elected and running Flexible-Algorithm Definition:
+ Source: 0000.0000.0002
+ Priority: 128
+ Equal to local: no
+ Local state: enabled
+ Calculation type: spf
+ Metric type: igp
+ Prefix-metric: disabled
+ Exclude SRLG: disabled
+ Exclude-any admin-group: not-set
+ Include-all admin-group: 0x00000018
+ Bit positions: 3, 4
+ Include-any admin-group: not-set
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/step9/show_mpls_table.ref b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step9/show_mpls_table.ref
new file mode 100644
index 0000000000..8ae983a790
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/step9/show_mpls_table.ref
@@ -0,0 +1,482 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20201":{
+ "inLabel":20201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20202":{
+ "inLabel":20202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20202,
+ "outLabelStack":[
+ 20202
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20401":{
+ "inLabel":20401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20402":{
+ "inLabel":20402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20501":{
+ "inLabel":20501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20502":{
+ "inLabel":20502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20502,
+ "outLabelStack":[
+ 20502
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20601":{
+ "inLabel":20601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20601,
+ "outLabelStack":[
+ 20601
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20602":{
+ "inLabel":20602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20701":{
+ "inLabel":20701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.13.0.1"
+ }
+ ]
+ },
+ "20702":{
+ "inLabel":20702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "21001":{
+ "inLabel":21001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21002":{
+ "inLabel":21002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21101":{
+ "inLabel":21101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21102":{
+ "inLabel":21102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21201":{
+ "inLabel":21201,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21202":{
+ "inLabel":21202,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21202,
+ "outLabelStack":[
+ 21202
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21302":{
+ "inLabel":21302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21401":{
+ "inLabel":21401,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21402":{
+ "inLabel":21402,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21501":{
+ "inLabel":21501,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21502":{
+ "inLabel":21502,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21502,
+ "outLabelStack":[
+ 21502
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21601":{
+ "inLabel":21601,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":21601,
+ "outLabelStack":[
+ 21601
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21602":{
+ "inLabel":21602,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ },
+ "21701":{
+ "inLabel":21701,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt1"
+ }
+ ]
+ },
+ "21702":{
+ "inLabel":21702,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "interface":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/rt3/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo1/rt3/zebra.conf
new file mode 100644
index 0000000000..fb45ee1282
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/rt3/zebra.conf
@@ -0,0 +1,39 @@
+log file zebra.log
+!
+hostname rt3
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map red bit-position 0
+affinity-map blue bit-position 1
+affinity-map green bit-position 2
+affinity-map yellow bit-position 3
+affinity-map orange bit-position 4
+!
+interface lo
+ ip address 3.3.3.3/32
+ ipv6 address 2001:db8:1000::3/128
+!
+interface eth-rt1
+ ip address 10.13.0.3/24
+ link-params
+ affinity green yellow orange
+ exit-link-params
+!
+interface eth-rt2
+ ip address 10.23.0.3/24
+ link-params
+ affinity blue yellow orange
+ exit-link-params
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo1/test_isis_sr_flex_algo_topo1.py b/tests/topotests/isis_sr_flex_algo_topo1/test_isis_sr_flex_algo_topo1.py
new file mode 100755
index 0000000000..85600beb0e
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo1/test_isis_sr_flex_algo_topo1.py
@@ -0,0 +1,583 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# Part of NetDEF Topology Tests
+#
+# Copyright 2021 by LINE Corporation, Hiroki Shirokura <hiroki.shirokura@linecorp.com>
+# Copyright 2023 6WIND S.A.
+
+"""
+test_isis_sr_flex_algo_topo1.py:
+
+[+] Flex-Algos 201 exclude red
+[+] Flex-Algos 202 exclude blue
+[+] Flex-Algos 203 exclude green
+[+] Flex-Algos 204 include-any blue green
+[+] Flex-Algos 205 include-any red green
+[+] Flex-Algos 206 include-any red blue
+[+] Flex-Algos 207 include-all yellow orange
+
+ +--------+ 10.12.0.0/24 +--------+
+ | | red | |
+ | RT1 |----------------| RT2 |
+ | | | |
+ +--------+ +--------+
+ 10.13.0.0/24 \\ / 10.23.0.0/24
+ green \\ / blue
+ yellow \\ / yellow
+ orange +--------+ orange
+ | |
+ | RT3 |
+ | |
+ +--------+
+"""
+
+import os
+import sys
+import pytest
+import json
+import tempfile
+from functools import partial
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+
+pytestmark = [pytest.mark.isisd]
+
+# Global multi-dimensional dictionary containing all expected outputs
+outputs = {}
+
+
+def build_topo(tgen):
+ "Build function"
+
+ def connect_routers(tgen, left_idx, right_idx):
+ left = "rt{}".format(left_idx)
+ right = "rt{}".format(right_idx)
+ switch = tgen.add_switch("s-{}-{}".format(left, right))
+ switch.add_link(tgen.gears[left], nodeif="eth-{}".format(right))
+ switch.add_link(tgen.gears[right], nodeif="eth-{}".format(left))
+ l_addr = "52:54:00:{}:{}:{}".format(left_idx, right_idx, left_idx)
+ tgen.gears[left].run("ip link set eth-{} down".format(right))
+ tgen.gears[left].run("ip link set eth-{} address {}".format(right, l_addr))
+ tgen.gears[left].run("ip link set eth-{} up".format(right))
+ r_addr = "52:54:00:{}:{}:{}".format(left_idx, right_idx, right_idx)
+ tgen.gears[right].run("ip link set eth-{} down".format(left))
+ tgen.gears[right].run("ip link set eth-{} address {}".format(left, r_addr))
+ tgen.gears[right].run("ip link set eth-{} up".format(left))
+
+ tgen.add_router("rt1")
+ tgen.add_router("rt2")
+ tgen.add_router("rt3")
+ connect_routers(tgen, 1, 2)
+ connect_routers(tgen, 2, 3)
+ connect_routers(tgen, 3, 1)
+
+ #
+ # Populate multi-dimensional dictionary containing all expected outputs
+ #
+ number_of_steps = 11
+ filenames = [
+ "show_mpls_table.ref",
+ "show_isis_flex_algo.ref",
+ ]
+ for rname in ["rt1", "rt2", "rt3"]:
+ outputs[rname] = {}
+ for step in range(1, number_of_steps + 1):
+ outputs[rname][step] = {}
+ for filename in filenames:
+ # Get snapshots relative to the expected network convergence
+ filename_pullpath = "{}/{}/step{}/{}".format(CWD, rname, step, filename)
+ outputs[rname][step][filename] = open(filename_pullpath).read()
+
+
+def setup_module(mod):
+ "Sets up the pytest environment"
+ tgen = Topogen(build_topo, mod.__name__)
+ frrdir = tgen.config.get(tgen.CONFIG_SECTION, "frrdir")
+ if not os.path.isfile(os.path.join(frrdir, "pathd")):
+ pytest.skip("pathd daemon wasn't built")
+ tgen.start_topology()
+ router_list = tgen.routers()
+
+ # For all registered routers, load the zebra configuration file
+ for rname, router in router_list.items():
+ 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)))
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ "Teardown the pytest environment"
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def setup_testcase(msg):
+ logger.info(msg)
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+ return tgen
+
+
+def router_compare_json_output(rname, command, reference):
+ "Compare router JSON output"
+
+ logger.info('Comparing router "%s" "%s" output', rname, command)
+
+ tgen = get_topogen()
+ expected = json.loads(reference)
+
+ # Run test function until we get an result. Wait at most 60 seconds.
+ test_func = partial(topotest.router_json_cmp, tgen.gears[rname], command, expected)
+ _, diff = topotest.run_and_expect(test_func, None, count=120, wait=0.5)
+ assertmsg = '"{}" JSON output mismatches the expected result'.format(rname)
+ assert diff is None, assertmsg
+
+
+def router_compare_output(rname, command, reference):
+ "Compare router output"
+
+ logger.info('Comparing router "%s" "%s" output', rname, command)
+
+ tgen = get_topogen()
+
+ # Run test function until we get an result. Wait at most 60 seconds.
+ test_func = partial(topotest.router_output_cmp, tgen.gears[rname], command, reference)
+ result, diff = topotest.run_and_expect(test_func, "", count=120, wait=0.5)
+ assertmsg = '{} command "{}" output mismatches the expected result:\n{}'.format(rname, command, diff)
+ assert result, assertmsg
+
+
+#
+# Step 1
+#
+# Test initial network convergenece
+#
+# All flex-algo are defined and its fib entries are installed
+#
+def test_step1_mpls_lfib():
+ logger.info("Test (step 1)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][1]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][1]["show_mpls_table.ref"])
+
+
+#
+# Step 2
+#
+# Action(s):
+# - Disable flex-algo-203 definition advertisement on rt1
+#
+# Expected change(s):
+# - Nothing
+#
+# Description:
+# No change occurs because it refers to the FAD set in rt2.
+#
+def test_step2_mpls_lfib():
+ logger.info("Test (step 2)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ tgen.gears["rt1"].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ flex-algo 203
+ no advertise-definition
+ """)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][2]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][2]["show_mpls_table.ref"])
+
+
+#
+# Step 3
+#
+# Action(s):
+# - Disable flex-algo-203 definition advertisement on rt2
+#
+# Expected change(s):
+# - rt1,rt2,rt3 should uninstall all Prefix-SIDs of flex-algo-203
+#
+# Description:
+# When all FADs are disappeared, all their prefix sid routes are withdrawn.
+#
+def test_step3_mpls_lfib():
+ logger.info("Test (step 3)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ tgen.gears["rt2"].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ flex-algo 203
+ no advertise-definition
+ """)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][3]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][3]["show_mpls_table.ref"])
+
+
+#
+# Step 4
+#
+# Action(s):
+# - Enable flex-algo-203 definition advertisement on rt2
+#
+# Expected change(s):
+# - rt1,rt2,rt3 should install all Prefix-SIDs of flex-algo-203
+#
+# Description:
+# Since the FAD is restored, the reachability to the Prefix-SID is restored.
+#
+def test_step4_mpls_lfib():
+ logger.info("Test (step 4)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ tgen.gears["rt2"].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ flex-algo 203
+ advertise-definition
+ """)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][4]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][4]["show_mpls_table.ref"])
+
+
+#
+# Step 5
+#
+# Action(s):
+# - Enable flex-algo-203 definition advertisement on rt1
+#
+# Expected change(s):
+# - Nothing
+#
+# Description:
+# This does not affect the FIB, since there is already a FAD for rt2.
+# However, the FAD owner will be changed from rt2 to rt1.
+#
+def test_step5_mpls_lfib():
+ logger.info("Test (step 5)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ tgen.gears["rt1"].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ flex-algo 203
+ advertise-definition
+ """)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][5]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][5]["show_mpls_table.ref"])
+
+
+#
+# Step 6
+#
+# Action(s):
+# - Disable flex-algo-203 SR-MPLS dataplane on rt1
+# - Disable flex-algo-203 SR-MPLS dataplane on rt2
+# - Disable flex-algo-203 SR-MPLS dataplane on rt3
+#
+# Expected change(s):
+# - rt1,rt2,rt3 should uninstall all Prefix-SIDs of flex-algo-203
+#
+# Description:
+# Clear the Flex-Algo 203 whole settings on each routers. All routes related
+# to it will be withdrawn.
+#
+def test_step6_mpls_lfib():
+ logger.info("Test (step 6)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ for rname in ["rt1", "rt2", "rt3"]:
+ tgen.gears[rname].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ flex-algo 203
+ no dataplane sr-mpls
+ """)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][6]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][6]["show_mpls_table.ref"])
+
+
+#
+# Step 7
+#
+# Action(s):
+# - Disable flex-algo-203 all configuration on rt1
+# - Disable flex-algo-203 all configuration on rt2
+# - Disable flex-algo-203 all configuration on rt3
+#
+# Expected change(s):
+# - rt1,rt2,rt3 should uninstall all Prefix-SIDs of flex-algo-203
+#
+# Description:
+# Clear the Flex-Algo 203 whole settings on each routers. All routes related
+# to it will be withdrawn.
+#
+def test_step7_mpls_lfib():
+ logger.info("Test (step 7)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ for rname in ["rt1", "rt2", "rt3"]:
+ tgen.gears[rname].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ no flex-algo 203
+ """)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][7]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][7]["show_mpls_table.ref"])
+
+#
+# Step 8
+#
+# Action(s):
+# - Enable flex-algo-203 all configuration on rt1
+# - Enable flex-algo-203 all configuration on rt2
+# - Enable flex-algo-203 all configuration on rt3
+#
+# Expected change(s):
+# - rt1,rt2,rt3 should install all Prefix-SIDs of flex-algo-203
+#
+# Description:
+# All configurations were backed.
+#
+def test_step8_mpls_lfib():
+ logger.info("Test (step 8)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ tgen.gears["rt1"].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ flex-algo 203
+ advertise-definition
+ affinity exclude-any green
+ dataplane sr-mpls
+ """)
+
+ tgen.gears["rt2"].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ flex-algo 203
+ advertise-definition
+ affinity exclude-any green
+ dataplane sr-mpls
+ """)
+
+ tgen.gears["rt3"].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ flex-algo 203
+ dataplane sr-mpls
+ """)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][8]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][8]["show_mpls_table.ref"])
+
+
+#
+# Step 9
+#
+# Action(s):
+# - Disable algorithm prefix-sid of algo-203 on rt1
+#
+# Expected change(s):
+# - rt1 should uninstall all Prefix-SIDs of flex-algo-203
+# - rt2 should uninstall Prefix-SIDs of rt1's flex-algo-203
+# - rt3 should uninstall Prefix-SIDs of rt1's flex-algo-203
+#
+def test_step9_mpls_lfib():
+ logger.info("Test (step 9)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ tgen.gears["rt1"].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ no segment-routing prefix 1.1.1.1/32 algorithm 203 index 301
+ no segment-routing prefix 2001:db8:1000::1/128 algorithm 203 index 1301
+ """)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][9]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][9]["show_mpls_table.ref"])
+
+
+#
+# Step 10
+#
+# Action(s):
+# - Enable algorithm prefix-sid of algo-203 on rt1
+#
+# Expected change(s):
+# - rt1 should install all Prefix-SIDs of flex-algo-203
+# - rt2 should install Prefix-SIDs of rt1's flex-algo-203
+# - rt3 should install Prefix-SIDs of rt1's flex-algo-203
+#
+def test_step10_mpls_lfib():
+ logger.info("Test (step 10)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ tgen.gears["rt1"].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ segment-routing prefix 1.1.1.1/32 algorithm 203 index 301
+ segment-routing prefix 2001:db8:1000::1/128 algorithm 203 index 1301
+ """)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][10]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][10]["show_mpls_table.ref"])
+
+
+#
+# Step 11
+#
+# Action(s):
+# - Update algorithm prefix-sid of algo-203 on rt1 from 301 to 311
+#
+# Expected change(s):
+# - rt2 should update Prefix-SIDs of rt1's flex-algo-203 from 301 to 311
+# - rt3 should update Prefix-SIDs of rt1's flex-algo-203 from 301 to 311
+#
+def test_step11_mpls_lfib():
+ logger.info("Test (step 11)")
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ tgen.gears["rt1"].vtysh_cmd(
+ """
+ configure terminal
+ router isis 1
+ segment-routing prefix 1.1.1.1/32 algorithm 203 index 311
+ segment-routing prefix 2001:db8:1000::1/128 algorithm 203 index 1311
+ """)
+
+ # For Developers
+ # tgen.mininet_cli()
+ for rname in ["rt1", "rt2", "rt3"]:
+ router_compare_output(
+ rname, "show isis flex-algo",
+ outputs[rname][11]["show_isis_flex_algo.ref"])
+ router_compare_json_output(
+ rname, "show mpls table json",
+ outputs[rname][11]["show_mpls_table.ref"])
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/README.md b/tests/topotests/isis_sr_flex_algo_topo2/README.md
new file mode 100644
index 0000000000..20282c49ee
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/README.md
@@ -0,0 +1,8 @@
+
+## test
+
+```
+fdk-enter rt9.pid iperf3 -s
+fdk-enter rt0.pid iperf3 -B 111.111.111.111 -c 222.222.222.222 -P20 -t 100000
+fdk-enter rt0.pid watch -n0.1 ip -s link show
+```
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/configure-te.sh b/tests/topotests/isis_sr_flex_algo_topo2/configure-te.sh
new file mode 100755
index 0000000000..527f05b60b
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/configure-te.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+if [ $# -ne 1 ]; then
+ echo "invalid command syntax" 1>&2
+ echo "Usage: $0 <0|128|129|130>" 1>&2
+ exit 1
+fi
+
+case "$1" in
+ 0 ) echo ;;
+ 128 ) echo ;;
+ 129 ) echo ;;
+ 130 ) echo ;;
+ * ) echo "error" ; exit ;;
+esac
+
+R0=$(cat /tmp/topotests/isis_sr_flex_algo_topo2.test_isis_sr_flex_algo_topo2/rt0.pid)
+R9=$(cat /tmp/topotests/isis_sr_flex_algo_topo2.test_isis_sr_flex_algo_topo2/rt9.pid)
+
+set -x
+
+cat <<EOF | nsenter -a -t $R0 vtysh
+conf te
+segment-routing
+ traffic-eng
+ policy color 1 endpoint 9.9.9.9
+ name sid-algorithm
+ binding-sid 111
+ candidate-path preference 100 name sid-algorithm explicit segment-list sid-algorithm-$1
+ exit
+ exit
+exit
+EOF
+
+cat <<EOF | nsenter -a -t $R9 vtysh
+conf te
+segment-routing
+ traffic-eng
+ policy color 1 endpoint 10.10.10.10
+ name sid-algorithm
+ binding-sid 222
+ candidate-path preference 100 name sid-algorithm explicit segment-list sid-algorithm-$1
+ exit
+ exit
+exit
+EOF
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt0/bgpd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt0/bgpd.conf
new file mode 100644
index 0000000000..3915bec385
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt0/bgpd.conf
@@ -0,0 +1,17 @@
+!
+router bgp 1
+ bgp router-id 10.10.10.10
+ no bgp network import-check
+ neighbor 9.9.9.9 remote-as 1
+ neighbor 9.9.9.9 update-source 10.10.10.10
+ !
+ address-family ipv4 unicast
+ network 10.255.0.0/24
+ neighbor 9.9.9.9 next-hop-self
+ neighbor 9.9.9.9 route-map sr-te in
+ exit-address-family
+!
+route-map sr-te permit 10
+ set sr-te color 1
+exit
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt0/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt0/isisd.conf
new file mode 100644
index 0000000000..cbf25504ef
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt0/isisd.conf
@@ -0,0 +1,56 @@
+password 1
+hostname rt0
+log file isisd.log
+!
+!debug isis events
+!debug isis spf-events
+!debug isis route-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip router isis 1
+ isis passive
+!
+interface eth-rt1
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt5
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.1000.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 128
+ dataplane sr-mpls
+ advertise-definition
+ !
+ flex-algo 129
+ dataplane sr-mpls
+ advertise-definition
+ !
+ flex-algo 130
+ dataplane sr-mpls
+ advertise-definition
+ affinity include-any blue
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 10.10.10.10/32 index 0
+ segment-routing prefix 10.10.10.10/32 algorithm 128 index 100
+ segment-routing prefix 10.10.10.10/32 algorithm 129 index 200
+ segment-routing prefix 10.10.10.10/32 algorithm 130 index 300
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt0/pathd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt0/pathd.conf
new file mode 100644
index 0000000000..c51b4e0247
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt0/pathd.conf
@@ -0,0 +1,20 @@
+log file pathd.log
+!
+hostname rt0
+!
+segment-routing
+ traffic-eng
+ segment-list sid-algorithm-0
+ index 10 mpls label 20009
+ exit
+ segment-list sid-algorithm-128
+ index 10 mpls label 20109
+ exit
+ segment-list sid-algorithm-129
+ index 10 mpls label 20209
+ exit
+ segment-list sid-algorithm-130
+ index 10 mpls label 20309
+ exit
+ exit
+exit
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt0/step1/route.json b/tests/topotests/isis_sr_flex_algo_topo2/rt0/step1/route.json
new file mode 100644
index 0000000000..51a1c25556
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt0/step1/route.json
@@ -0,0 +1,438 @@
+{
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20003,
+ "outLabelStack":[
+ 20003
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20004":{
+ "inLabel":20004,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20005":{
+ "inLabel":20005,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20006":{
+ "inLabel":20006,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20007":{
+ "inLabel":20007,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20007,
+ "outLabelStack":[
+ 20007
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20008":{
+ "inLabel":20008,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20009":{
+ "inLabel":20009,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20009,
+ "outLabelStack":[
+ 20009
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20009,
+ "outLabelStack":[
+ 20009
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20103,
+ "outLabelStack":[
+ 20103
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20104":{
+ "inLabel":20104,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20104,
+ "outLabelStack":[
+ 20104
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20109":{
+ "inLabel":20109,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20109,
+ "outLabelStack":[
+ 20109
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20205":{
+ "inLabel":20205,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20206":{
+ "inLabel":20206,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20206,
+ "outLabelStack":[
+ 20206
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20207":{
+ "inLabel":20207,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20207,
+ "outLabelStack":[
+ 20207
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20208":{
+ "inLabel":20208,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20208,
+ "outLabelStack":[
+ 20208
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20209":{
+ "inLabel":20209,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20209,
+ "outLabelStack":[
+ 20209
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20302,
+ "outLabelStack":[
+ 20302
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ },
+ "20305":{
+ "inLabel":20305,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20306":{
+ "inLabel":20306,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20306,
+ "outLabelStack":[
+ 20306
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20307":{
+ "inLabel":20307,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20307,
+ "outLabelStack":[
+ 20307
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ }
+ ]
+ },
+ "20309":{
+ "inLabel":20309,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20309,
+ "outLabelStack":[
+ 20309
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.5"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20309,
+ "outLabelStack":[
+ 20309
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.1"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt0/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt0/zebra.conf
new file mode 100644
index 0000000000..89837d4cf5
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt0/zebra.conf
@@ -0,0 +1,34 @@
+log file zebra.log
+!
+hostname rt0
+!
+!log stdout notifications
+!log monitor notifications
+!log commands
+!
+debug zebra packet
+debug zebra dplane
+debug zebra kernel
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip address 10.10.10.10/32
+!
+interface eth-rt1
+ ip address 10.1.0.10/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt5
+ ip address 10.5.0.10/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt1/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt1/isisd.conf
new file mode 100644
index 0000000000..b6bba0c1c3
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt1/isisd.conf
@@ -0,0 +1,60 @@
+password 1
+hostname rt1
+log file isisd.log
+!
+!debug isis events
+!debug isis spf-events
+!debug isis route-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip router isis 1
+ isis passive
+!
+interface eth-rt0
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt2
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt4
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt5
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.1001.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 128
+ dataplane sr-mpls
+ flex-algo 130
+ dataplane sr-mpls
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 1.1.1.1/32 index 1
+ segment-routing prefix 1.1.1.1/32 algorithm 128 index 101
+ segment-routing prefix 1.1.1.1/32 algorithm 129 index 201
+ segment-routing prefix 1.1.1.1/32 algorithm 130 index 301
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt1/step1/route.json b/tests/topotests/isis_sr_flex_algo_topo2/rt1/step1/route.json
new file mode 100644
index 0000000000..50066250b8
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt1/step1/route.json
@@ -0,0 +1,428 @@
+{
+ "20000":{
+ "inLabel":20000,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.10"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20003,
+ "outLabelStack":[
+ 20003
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.4"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20003,
+ "outLabelStack":[
+ 20003
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20004":{
+ "inLabel":20004,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.4"
+ }
+ ]
+ },
+ "20005":{
+ "inLabel":20005,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.15.0.5"
+ }
+ ]
+ },
+ "20006":{
+ "inLabel":20006,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.15.0.5"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20007":{
+ "inLabel":20007,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20007,
+ "outLabelStack":[
+ 20007
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.15.0.5"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20007,
+ "outLabelStack":[
+ 20007
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.4"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20007,
+ "outLabelStack":[
+ 20007
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20008":{
+ "inLabel":20008,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.15.0.5"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.4"
+ }
+ ]
+ },
+ "20009":{
+ "inLabel":20009,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20009,
+ "outLabelStack":[
+ 20009
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.4"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20009,
+ "outLabelStack":[
+ 20009
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20100":{
+ "inLabel":20100,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.10"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20103,
+ "outLabelStack":[
+ 20103
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.4"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20103,
+ "outLabelStack":[
+ 20103
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20104":{
+ "inLabel":20104,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.4"
+ }
+ ]
+ },
+ "20109":{
+ "inLabel":20109,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20109,
+ "outLabelStack":[
+ 20109
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.4"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20109,
+ "outLabelStack":[
+ 20109
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20300":{
+ "inLabel":20300,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.10"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ },
+ "20305":{
+ "inLabel":20305,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20305,
+ "outLabelStack":[
+ 20305
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.10"
+ }
+ ]
+ },
+ "20306":{
+ "inLabel":20306,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20306,
+ "outLabelStack":[
+ 20306
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.10"
+ }
+ ]
+ },
+ "20307":{
+ "inLabel":20307,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20307,
+ "outLabelStack":[
+ 20307
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20307,
+ "outLabelStack":[
+ 20307
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.1.0.10"
+ }
+ ]
+ },
+ "20309":{
+ "inLabel":20309,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20309,
+ "outLabelStack":[
+ 20309
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt1/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt1/zebra.conf
new file mode 100644
index 0000000000..25a96290ae
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt1/zebra.conf
@@ -0,0 +1,40 @@
+log file zebra.log
+!
+hostname rt1
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip address 1.1.1.1/32
+!
+interface eth-rt0
+ ip address 10.1.0.1/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt2
+ ip address 10.12.0.1/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt4
+ ip address 10.14.0.1/24
+!
+interface eth-rt5
+ ip address 10.15.0.1/24
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt2/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt2/isisd.conf
new file mode 100644
index 0000000000..f051a68e21
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt2/isisd.conf
@@ -0,0 +1,54 @@
+password 1
+hostname rt2
+log file isisd.log
+!
+!debug isis events
+!debug isis route-events
+!debug isis spf-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip router isis 1
+ isis passive
+!
+interface eth-rt1
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt3
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt6
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.1002.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 128
+ dataplane sr-mpls
+ flex-algo 130
+ dataplane sr-mpls
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 2.2.2.2/32 index 2
+ segment-routing prefix 2.2.2.2/32 algorithm 128 index 102
+ segment-routing prefix 2.2.2.2/32 algorithm 129 index 202
+ segment-routing prefix 2.2.2.2/32 algorithm 130 index 302
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt2/step1/route.json b/tests/topotests/isis_sr_flex_algo_topo2/rt2/step1/route.json
new file mode 100644
index 0000000000..679b41db03
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt2/step1/route.json
@@ -0,0 +1,408 @@
+{
+ "20000":{
+ "inLabel":20000,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20000,
+ "outLabelStack":[
+ 20000
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20004":{
+ "inLabel":20004,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20005":{
+ "inLabel":20005,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20005,
+ "outLabelStack":[
+ 20005
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.26.0.6"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20005,
+ "outLabelStack":[
+ 20005
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20006":{
+ "inLabel":20006,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.26.0.6"
+ }
+ ]
+ },
+ "20007":{
+ "inLabel":20007,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20007,
+ "outLabelStack":[
+ 20007
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.26.0.6"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20007,
+ "outLabelStack":[
+ 20007
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20008":{
+ "inLabel":20008,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.26.0.6"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20009":{
+ "inLabel":20009,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20009,
+ "outLabelStack":[
+ 20009
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20100":{
+ "inLabel":20100,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20100,
+ "outLabelStack":[
+ 20100
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20104":{
+ "inLabel":20104,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20104,
+ "outLabelStack":[
+ 20104
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20104,
+ "outLabelStack":[
+ 20104
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20109":{
+ "inLabel":20109,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20109,
+ "outLabelStack":[
+ 20109
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20300":{
+ "inLabel":20300,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20300,
+ "outLabelStack":[
+ 20300
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20305":{
+ "inLabel":20305,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20305,
+ "outLabelStack":[
+ 20305
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20306":{
+ "inLabel":20306,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20306,
+ "outLabelStack":[
+ 20306
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20306,
+ "outLabelStack":[
+ 20306
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.12.0.1"
+ }
+ ]
+ },
+ "20307":{
+ "inLabel":20307,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20307,
+ "outLabelStack":[
+ 20307
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ },
+ "20309":{
+ "inLabel":20309,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20309,
+ "outLabelStack":[
+ 20309
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt2/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt2/zebra.conf
new file mode 100644
index 0000000000..d739a732a9
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt2/zebra.conf
@@ -0,0 +1,37 @@
+log file zebra.log
+!
+hostname rt2
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip address 2.2.2.2/32
+!
+interface eth-rt1
+ ip address 10.12.0.2/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt3
+ ip address 10.23.0.2/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt6
+ ip address 10.26.0.2/24
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt3/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt3/isisd.conf
new file mode 100644
index 0000000000..644e656bfd
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt3/isisd.conf
@@ -0,0 +1,60 @@
+password 1
+hostname rt3
+log file isisd.log
+!
+!debug isis events
+!debug isis route-events
+!debug isis spf-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip router isis 1
+ isis passive
+!
+interface eth-rt2
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt4
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt7
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt9
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.1003.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 128
+ dataplane sr-mpls
+ flex-algo 130
+ dataplane sr-mpls
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 3.3.3.3/32 index 3
+ segment-routing prefix 3.3.3.3/32 algorithm 128 index 103
+ segment-routing prefix 3.3.3.3/32 algorithm 129 index 203
+ segment-routing prefix 3.3.3.3/32 algorithm 130 index 303
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt3/step1/route.json b/tests/topotests/isis_sr_flex_algo_topo2/rt3/step1/route.json
new file mode 100644
index 0000000000..f930faa61f
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt3/step1/route.json
@@ -0,0 +1,428 @@
+{
+ "20000":{
+ "inLabel":20000,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20000,
+ "outLabelStack":[
+ 20000
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.4"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20000,
+ "outLabelStack":[
+ 20000
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20001,
+ "outLabelStack":[
+ 20001
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.4"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20001,
+ "outLabelStack":[
+ 20001
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20004":{
+ "inLabel":20004,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.4"
+ }
+ ]
+ },
+ "20005":{
+ "inLabel":20005,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20005,
+ "outLabelStack":[
+ 20005
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.37.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20005,
+ "outLabelStack":[
+ 20005
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.4"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20005,
+ "outLabelStack":[
+ 20005
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20006":{
+ "inLabel":20006,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.37.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20007":{
+ "inLabel":20007,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.37.0.7"
+ }
+ ]
+ },
+ "20008":{
+ "inLabel":20008,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.37.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.4"
+ }
+ ]
+ },
+ "20009":{
+ "inLabel":20009,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.9"
+ }
+ ]
+ },
+ "20100":{
+ "inLabel":20100,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20100,
+ "outLabelStack":[
+ 20100
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.4"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20100,
+ "outLabelStack":[
+ 20100
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.4"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20104":{
+ "inLabel":20104,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.4"
+ }
+ ]
+ },
+ "20109":{
+ "inLabel":20109,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.9"
+ }
+ ]
+ },
+ "20300":{
+ "inLabel":20300,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20300,
+ "outLabelStack":[
+ 20300
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20305":{
+ "inLabel":20305,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20305,
+ "outLabelStack":[
+ 20305
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.9"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20305,
+ "outLabelStack":[
+ 20305
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.23.0.2"
+ }
+ ]
+ },
+ "20306":{
+ "inLabel":20306,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20306,
+ "outLabelStack":[
+ 20306
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.9"
+ }
+ ]
+ },
+ "20307":{
+ "inLabel":20307,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20307,
+ "outLabelStack":[
+ 20307
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.9"
+ }
+ ]
+ },
+ "20309":{
+ "inLabel":20309,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.9"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt3/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt3/zebra.conf
new file mode 100644
index 0000000000..5c3bed0763
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt3/zebra.conf
@@ -0,0 +1,40 @@
+log file zebra.log
+!
+hostname rt3
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip address 3.3.3.3/32
+!
+interface eth-rt2
+ ip address 10.23.0.3/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt4
+ ip address 10.34.0.3/24
+!
+interface eth-rt7
+ ip address 10.37.0.3/24
+!
+interface eth-rt9
+ ip address 10.39.0.3/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt4/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt4/isisd.conf
new file mode 100644
index 0000000000..1ab200fbd8
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt4/isisd.conf
@@ -0,0 +1,52 @@
+password 1
+hostname rt4
+log file isisd.log
+!
+!debug isis events
+!debug isis spf-events
+!debug isis route-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip router isis 1
+ isis passive
+!
+interface eth-rt1
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt3
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt8
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.1004.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 128
+ dataplane sr-mpls
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 4.4.4.4/32 index 4
+ segment-routing prefix 4.4.4.4/32 algorithm 128 index 104
+ segment-routing prefix 4.4.4.4/32 algorithm 129 index 204
+ segment-routing prefix 4.4.4.4/32 algorithm 130 index 304
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt4/step1/route.json b/tests/topotests/isis_sr_flex_algo_topo2/rt4/step1/route.json
new file mode 100644
index 0000000000..141e40d455
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt4/step1/route.json
@@ -0,0 +1,286 @@
+{
+ "20000":{
+ "inLabel":20000,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20000,
+ "outLabelStack":[
+ 20000
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.1"
+ }
+ ]
+ },
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.3"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.3"
+ }
+ ]
+ },
+ "20005":{
+ "inLabel":20005,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20005,
+ "outLabelStack":[
+ 20005
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.48.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20005,
+ "outLabelStack":[
+ 20005
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.1"
+ }
+ ]
+ },
+ "20006":{
+ "inLabel":20006,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.48.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.3"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.1"
+ }
+ ]
+ },
+ "20007":{
+ "inLabel":20007,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20007,
+ "outLabelStack":[
+ 20007
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.48.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20007,
+ "outLabelStack":[
+ 20007
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.3"
+ }
+ ]
+ },
+ "20008":{
+ "inLabel":20008,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.48.0.8"
+ }
+ ]
+ },
+ "20009":{
+ "inLabel":20009,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20009,
+ "outLabelStack":[
+ 20009
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.3"
+ }
+ ]
+ },
+ "20100":{
+ "inLabel":20100,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20100,
+ "outLabelStack":[
+ 20100
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.1"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.1"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.3"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.14.0.1"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.3"
+ }
+ ]
+ },
+ "20109":{
+ "inLabel":20109,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20109,
+ "outLabelStack":[
+ 20109
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.34.0.3"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt4/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt4/zebra.conf
new file mode 100644
index 0000000000..9c00013e70
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt4/zebra.conf
@@ -0,0 +1,31 @@
+log file zebra.log
+!
+hostname rt4
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip address 4.4.4.4/32
+!
+interface eth-rt1
+ ip address 10.14.0.4/24
+!
+interface eth-rt3
+ ip address 10.34.0.4/24
+!
+interface eth-rt8
+ ip address 10.48.0.4/24
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt5/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt5/isisd.conf
new file mode 100644
index 0000000000..54cc37711e
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt5/isisd.conf
@@ -0,0 +1,60 @@
+password 1
+hostname rt5
+log file isisd.log
+!
+!debug isis events
+!debug isis spf-events
+!debug isis route-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip router isis 1
+ isis passive
+!
+interface eth-rt0
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt1
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt6
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt8
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.1005.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 129
+ dataplane sr-mpls
+ flex-algo 130
+ dataplane sr-mpls
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 5.5.5.5/32 index 5
+ segment-routing prefix 5.5.5.5/32 algorithm 128 index 105
+ segment-routing prefix 5.5.5.5/32 algorithm 129 index 205
+ segment-routing prefix 5.5.5.5/32 algorithm 130 index 305
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt5/step1/route.json b/tests/topotests/isis_sr_flex_algo_topo2/rt5/step1/route.json
new file mode 100644
index 0000000000..82ebfc075f
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt5/step1/route.json
@@ -0,0 +1,428 @@
+{
+ "20000":{
+ "inLabel":20000,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.10"
+ }
+ ]
+ },
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.15.0.1"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.15.0.1"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20003,
+ "outLabelStack":[
+ 20003
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20003,
+ "outLabelStack":[
+ 20003
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20003,
+ "outLabelStack":[
+ 20003
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.15.0.1"
+ }
+ ]
+ },
+ "20004":{
+ "inLabel":20004,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.15.0.1"
+ }
+ ]
+ },
+ "20006":{
+ "inLabel":20006,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ }
+ ]
+ },
+ "20007":{
+ "inLabel":20007,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20007,
+ "outLabelStack":[
+ 20007
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20007,
+ "outLabelStack":[
+ 20007
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ }
+ ]
+ },
+ "20008":{
+ "inLabel":20008,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.8"
+ }
+ ]
+ },
+ "20009":{
+ "inLabel":20009,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20009,
+ "outLabelStack":[
+ 20009
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20009,
+ "outLabelStack":[
+ 20009
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ }
+ ]
+ },
+ "20200":{
+ "inLabel":20200,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.10"
+ }
+ ]
+ },
+ "20206":{
+ "inLabel":20206,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ }
+ ]
+ },
+ "20207":{
+ "inLabel":20207,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20207,
+ "outLabelStack":[
+ 20207
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20207,
+ "outLabelStack":[
+ 20207
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ }
+ ]
+ },
+ "20208":{
+ "inLabel":20208,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.8"
+ }
+ ]
+ },
+ "20209":{
+ "inLabel":20209,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20209,
+ "outLabelStack":[
+ 20209
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20209,
+ "outLabelStack":[
+ 20209
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ }
+ ]
+ },
+ "20300":{
+ "inLabel":20300,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.10"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.10"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20302,
+ "outLabelStack":[
+ 20302
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.10"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.5.0.10"
+ }
+ ]
+ },
+ "20306":{
+ "inLabel":20306,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ }
+ ]
+ },
+ "20307":{
+ "inLabel":20307,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20307,
+ "outLabelStack":[
+ 20307
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ }
+ ]
+ },
+ "20309":{
+ "inLabel":20309,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20309,
+ "outLabelStack":[
+ 20309
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.6"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt5/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt5/zebra.conf
new file mode 100644
index 0000000000..61c599db7b
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt5/zebra.conf
@@ -0,0 +1,40 @@
+log file zebra.log
+!
+hostname rt5
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip address 5.5.5.5/32
+!
+interface eth-rt0
+ ip address 10.5.0.5/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt1
+ ip address 10.15.0.5/24
+!
+interface eth-rt6
+ ip address 10.56.0.5/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt8
+ ip address 10.58.0.5/24
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt6/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt6/isisd.conf
new file mode 100644
index 0000000000..fc8660cfa6
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt6/isisd.conf
@@ -0,0 +1,54 @@
+password 1
+hostname rt6
+log file isisd.log
+!
+!debug isis events
+!debug isis spf-events
+!debug isis route-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip router isis 1
+ isis passive
+!
+interface eth-rt2
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt5
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt7
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.1006.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 129
+ dataplane sr-mpls
+ flex-algo 130
+ dataplane sr-mpls
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 6.6.6.6/32 index 6
+ segment-routing prefix 6.6.6.6/32 algorithm 128 index 106
+ segment-routing prefix 6.6.6.6/32 algorithm 129 index 206
+ segment-routing prefix 6.6.6.6/32 algorithm 130 index 306
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt6/step1/route.json b/tests/topotests/isis_sr_flex_algo_topo2/rt6/step1/route.json
new file mode 100644
index 0000000000..2cc7277b41
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt6/step1/route.json
@@ -0,0 +1,408 @@
+{
+ "20000":{
+ "inLabel":20000,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20000,
+ "outLabelStack":[
+ 20000
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ }
+ ]
+ },
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20001,
+ "outLabelStack":[
+ 20001
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20001,
+ "outLabelStack":[
+ 20001
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.26.0.2"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.26.0.2"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20003,
+ "outLabelStack":[
+ 20003
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20003,
+ "outLabelStack":[
+ 20003
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.26.0.2"
+ }
+ ]
+ },
+ "20004":{
+ "inLabel":20004,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.26.0.2"
+ }
+ ]
+ },
+ "20005":{
+ "inLabel":20005,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ }
+ ]
+ },
+ "20007":{
+ "inLabel":20007,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ }
+ ]
+ },
+ "20008":{
+ "inLabel":20008,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ }
+ ]
+ },
+ "20009":{
+ "inLabel":20009,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20009,
+ "outLabelStack":[
+ 20009
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ }
+ ]
+ },
+ "20200":{
+ "inLabel":20200,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20200,
+ "outLabelStack":[
+ 20200
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ }
+ ]
+ },
+ "20205":{
+ "inLabel":20205,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ }
+ ]
+ },
+ "20207":{
+ "inLabel":20207,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ }
+ ]
+ },
+ "20208":{
+ "inLabel":20208,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20208,
+ "outLabelStack":[
+ 20208
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20208,
+ "outLabelStack":[
+ 20208
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ }
+ ]
+ },
+ "20209":{
+ "inLabel":20209,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20209,
+ "outLabelStack":[
+ 20209
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ }
+ ]
+ },
+ "20300":{
+ "inLabel":20300,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20300,
+ "outLabelStack":[
+ 20300
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20302,
+ "outLabelStack":[
+ 20302
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20302,
+ "outLabelStack":[
+ 20302
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ }
+ ]
+ },
+ "20305":{
+ "inLabel":20305,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.56.0.5"
+ }
+ ]
+ },
+ "20307":{
+ "inLabel":20307,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ }
+ ]
+ },
+ "20309":{
+ "inLabel":20309,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20309,
+ "outLabelStack":[
+ 20309
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.7"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt6/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt6/zebra.conf
new file mode 100644
index 0000000000..b63401e114
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt6/zebra.conf
@@ -0,0 +1,37 @@
+log file zebra.log
+!
+hostname rt6
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip address 6.6.6.6/32
+!
+interface eth-rt2
+ ip address 10.26.0.6/24
+!
+interface eth-rt5
+ ip address 10.56.0.6/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt7
+ ip address 10.67.0.6/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt7/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt7/isisd.conf
new file mode 100644
index 0000000000..10dc9812a4
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt7/isisd.conf
@@ -0,0 +1,60 @@
+password 1
+hostname rt7
+log file isisd.log
+!
+!debug isis events
+!debug isis spf-events
+!debug isis route-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip router isis 1
+ isis passive
+!
+interface eth-rt3
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt6
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt8
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt9
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.1007.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 129
+ dataplane sr-mpls
+ flex-algo 130
+ dataplane sr-mpls
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 7.7.7.7/32 index 7
+ segment-routing prefix 7.7.7.7/32 algorithm 128 index 107
+ segment-routing prefix 7.7.7.7/32 algorithm 129 index 207
+ segment-routing prefix 7.7.7.7/32 algorithm 130 index 307
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt7/step1/route.json b/tests/topotests/isis_sr_flex_algo_topo2/rt7/step1/route.json
new file mode 100644
index 0000000000..aeaa6046ab
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt7/step1/route.json
@@ -0,0 +1,428 @@
+{
+ "20000":{
+ "inLabel":20000,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20000,
+ "outLabelStack":[
+ 20000
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20000,
+ "outLabelStack":[
+ 20000
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ }
+ ]
+ },
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20001,
+ "outLabelStack":[
+ 20001
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20001,
+ "outLabelStack":[
+ 20001
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20001,
+ "outLabelStack":[
+ 20001
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.37.0.3"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.37.0.3"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.37.0.3"
+ }
+ ]
+ },
+ "20004":{
+ "inLabel":20004,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.37.0.3"
+ }
+ ]
+ },
+ "20005":{
+ "inLabel":20005,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20005,
+ "outLabelStack":[
+ 20005
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20005,
+ "outLabelStack":[
+ 20005
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ }
+ ]
+ },
+ "20006":{
+ "inLabel":20006,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ }
+ ]
+ },
+ "20008":{
+ "inLabel":20008,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.8"
+ }
+ ]
+ },
+ "20009":{
+ "inLabel":20009,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.9"
+ }
+ ]
+ },
+ "20200":{
+ "inLabel":20200,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20200,
+ "outLabelStack":[
+ 20200
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20200,
+ "outLabelStack":[
+ 20200
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ }
+ ]
+ },
+ "20205":{
+ "inLabel":20205,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20205,
+ "outLabelStack":[
+ 20205
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.8"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20205,
+ "outLabelStack":[
+ 20205
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ }
+ ]
+ },
+ "20206":{
+ "inLabel":20206,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ }
+ ]
+ },
+ "20208":{
+ "inLabel":20208,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.8"
+ }
+ ]
+ },
+ "20209":{
+ "inLabel":20209,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.9"
+ }
+ ]
+ },
+ "20300":{
+ "inLabel":20300,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20300,
+ "outLabelStack":[
+ 20300
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.9"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20302,
+ "outLabelStack":[
+ 20302
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.9"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20303,
+ "outLabelStack":[
+ 20303
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.9"
+ }
+ ]
+ },
+ "20305":{
+ "inLabel":20305,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20305,
+ "outLabelStack":[
+ 20305
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ }
+ ]
+ },
+ "20306":{
+ "inLabel":20306,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.67.0.6"
+ }
+ ]
+ },
+ "20309":{
+ "inLabel":20309,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.9"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt7/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt7/zebra.conf
new file mode 100644
index 0000000000..b5a28c7f1a
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt7/zebra.conf
@@ -0,0 +1,40 @@
+log file zebra.log
+!
+hostname rt7
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip address 7.7.7.7/32
+!
+interface eth-rt3
+ ip address 10.37.0.7/24
+!
+interface eth-rt6
+ ip address 10.67.0.7/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt8
+ ip address 10.78.0.7/24
+!
+interface eth-rt9
+ ip address 10.79.0.7/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt8/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt8/isisd.conf
new file mode 100644
index 0000000000..4ca45a8ad9
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt8/isisd.conf
@@ -0,0 +1,50 @@
+password 1
+hostname rt8
+log file isisd.log
+!
+!debug isis events
+!debug isis spf-events
+!debug isis route-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+interface lo
+ ip router isis 1
+ isis passive
+!
+interface eth-rt4
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt5
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt7
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.1008.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 129
+ dataplane sr-mpls
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 8.8.8.8/32 index 8
+ segment-routing prefix 8.8.8.8/32 algorithm 128 index 108
+ segment-routing prefix 8.8.8.8/32 algorithm 129 index 208
+ segment-routing prefix 8.8.8.8/32 algorithm 130 index 308
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt8/step1/route.json b/tests/topotests/isis_sr_flex_algo_topo2/rt8/step1/route.json
new file mode 100644
index 0000000000..27470b76cc
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt8/step1/route.json
@@ -0,0 +1,286 @@
+{
+ "20000":{
+ "inLabel":20000,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20000,
+ "outLabelStack":[
+ 20000
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.5"
+ }
+ ]
+ },
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20001,
+ "outLabelStack":[
+ 20001
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.5"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20001,
+ "outLabelStack":[
+ 20001
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.48.0.4"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.5"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.48.0.4"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20003,
+ "outLabelStack":[
+ 20003
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20003,
+ "outLabelStack":[
+ 20003
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.48.0.4"
+ }
+ ]
+ },
+ "20004":{
+ "inLabel":20004,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.48.0.4"
+ }
+ ]
+ },
+ "20005":{
+ "inLabel":20005,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.5"
+ }
+ ]
+ },
+ "20006":{
+ "inLabel":20006,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.5"
+ }
+ ]
+ },
+ "20007":{
+ "inLabel":20007,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.7"
+ }
+ ]
+ },
+ "20009":{
+ "inLabel":20009,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20009,
+ "outLabelStack":[
+ 20009
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.7"
+ }
+ ]
+ },
+ "20200":{
+ "inLabel":20200,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20200,
+ "outLabelStack":[
+ 20200
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.5"
+ }
+ ]
+ },
+ "20205":{
+ "inLabel":20205,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.5"
+ }
+ ]
+ },
+ "20206":{
+ "inLabel":20206,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20206,
+ "outLabelStack":[
+ 20206
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20206,
+ "outLabelStack":[
+ 20206
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.58.0.5"
+ }
+ ]
+ },
+ "20207":{
+ "inLabel":20207,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.7"
+ }
+ ]
+ },
+ "20209":{
+ "inLabel":20209,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20209,
+ "outLabelStack":[
+ 20209
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.78.0.7"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt8/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt8/zebra.conf
new file mode 100644
index 0000000000..dd63f8cc2f
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt8/zebra.conf
@@ -0,0 +1,29 @@
+log file zebra.log
+!
+hostname rt8
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+interface lo
+ ip address 8.8.8.8/32
+!
+interface eth-rt4
+ ip address 10.48.0.8/24
+!
+interface eth-rt5
+ ip address 10.58.0.8/24
+!
+interface eth-rt7
+ ip address 10.78.0.8/24
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt9/bgpd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt9/bgpd.conf
new file mode 100644
index 0000000000..386d8118e7
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt9/bgpd.conf
@@ -0,0 +1,17 @@
+!
+router bgp 1
+ bgp router-id 9.9.9.9
+ no bgp network import-check
+ neighbor 10.10.10.10 remote-as 1
+ neighbor 10.10.10.10 update-source 9.9.9.9
+ !
+ address-family ipv4 unicast
+ network 10.255.9.0/24
+ neighbor 10.10.10.10 next-hop-self
+ neighbor 10.10.10.10 route-map sr-te in
+ exit-address-family
+!
+route-map sr-te permit 10
+ set sr-te color 1
+exit
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt9/isisd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt9/isisd.conf
new file mode 100644
index 0000000000..89eab274c7
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt9/isisd.conf
@@ -0,0 +1,56 @@
+password 1
+hostname rt9
+log file isisd.log
+!
+!debug isis events
+!debug isis spf-events
+!debug isis route-events
+!debug isis sr-events
+!debug isis lsp-gen
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip router isis 1
+ isis passive
+!
+interface eth-rt3
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+interface eth-rt7
+ ip router isis 1
+ isis hello-multiplier 3
+ isis network point-to-point
+ isis circuit-type level-1
+!
+router isis 1
+ lsp-gen-interval 2
+ net 49.0000.0000.0000.1009.00
+ is-type level-1
+ topology ipv6-unicast
+ mpls-te on
+ !
+ flex-algo 128
+ dataplane sr-mpls
+ advertise-definition
+ !
+ flex-algo 129
+ dataplane sr-mpls
+ advertise-definition
+ !
+ flex-algo 130
+ dataplane sr-mpls
+ advertise-definition
+ affinity include-any blue
+ !
+ segment-routing on
+ segment-routing global-block 20000 23999
+ segment-routing node-msd 8
+ segment-routing prefix 9.9.9.9/32 index 9
+ segment-routing prefix 9.9.9.9/32 algorithm 128 index 109
+ segment-routing prefix 9.9.9.9/32 algorithm 129 index 209
+ segment-routing prefix 9.9.9.9/32 algorithm 130 index 309
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt9/pathd.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt9/pathd.conf
new file mode 100644
index 0000000000..3f9a8d9059
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt9/pathd.conf
@@ -0,0 +1,20 @@
+log file pathd.log
+!
+hostname rt9
+!
+segment-routing
+ traffic-eng
+ segment-list sid-algorithm-0
+ index 10 mpls label 20000
+ exit
+ segment-list sid-algorithm-128
+ index 10 mpls label 20100
+ exit
+ segment-list sid-algorithm-129
+ index 10 mpls label 20200
+ exit
+ segment-list sid-algorithm-130
+ index 10 mpls label 20300
+ exit
+ exit
+exit
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt9/step1/route.json b/tests/topotests/isis_sr_flex_algo_topo2/rt9/step1/route.json
new file mode 100644
index 0000000000..e98680c5dc
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt9/step1/route.json
@@ -0,0 +1,438 @@
+{
+ "20000":{
+ "inLabel":20000,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20000,
+ "outLabelStack":[
+ 20000
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20000,
+ "outLabelStack":[
+ 20000
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20001":{
+ "inLabel":20001,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20001,
+ "outLabelStack":[
+ 20001
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20002":{
+ "inLabel":20002,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20002,
+ "outLabelStack":[
+ 20002
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20003":{
+ "inLabel":20003,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20004":{
+ "inLabel":20004,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20004,
+ "outLabelStack":[
+ 20004
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20005":{
+ "inLabel":20005,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20005,
+ "outLabelStack":[
+ 20005
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20006":{
+ "inLabel":20006,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20006,
+ "outLabelStack":[
+ 20006
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20007":{
+ "inLabel":20007,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20008":{
+ "inLabel":20008,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20008,
+ "outLabelStack":[
+ 20008
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20100":{
+ "inLabel":20100,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20100,
+ "outLabelStack":[
+ 20100
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20101":{
+ "inLabel":20101,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20101,
+ "outLabelStack":[
+ 20101
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20102":{
+ "inLabel":20102,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20102,
+ "outLabelStack":[
+ 20102
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20103":{
+ "inLabel":20103,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20104":{
+ "inLabel":20104,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20104,
+ "outLabelStack":[
+ 20104
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20200":{
+ "inLabel":20200,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20200,
+ "outLabelStack":[
+ 20200
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20205":{
+ "inLabel":20205,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20205,
+ "outLabelStack":[
+ 20205
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20206":{
+ "inLabel":20206,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20206,
+ "outLabelStack":[
+ 20206
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20207":{
+ "inLabel":20207,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20208":{
+ "inLabel":20208,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20208,
+ "outLabelStack":[
+ 20208
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20300":{
+ "inLabel":20300,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20300,
+ "outLabelStack":[
+ 20300
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ },
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20300,
+ "outLabelStack":[
+ 20300
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20301":{
+ "inLabel":20301,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20301,
+ "outLabelStack":[
+ 20301
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20302":{
+ "inLabel":20302,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20302,
+ "outLabelStack":[
+ 20302
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20303":{
+ "inLabel":20303,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.39.0.3"
+ }
+ ]
+ },
+ "20305":{
+ "inLabel":20305,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20305,
+ "outLabelStack":[
+ 20305
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20306":{
+ "inLabel":20306,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":20306,
+ "outLabelStack":[
+ 20306
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ },
+ "20307":{
+ "inLabel":20307,
+ "installed":true,
+ "nexthops":[
+ {
+ "type":"SR (IS-IS)",
+ "outLabel":3,
+ "outLabelStack":[
+ 3
+ ],
+ "distance":150,
+ "installed":true,
+ "nexthop":"10.79.0.7"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/rt9/zebra.conf b/tests/topotests/isis_sr_flex_algo_topo2/rt9/zebra.conf
new file mode 100644
index 0000000000..378a1969be
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/rt9/zebra.conf
@@ -0,0 +1,34 @@
+log file zebra.log
+!
+hostname rt9
+!
+log stdout notifications
+log monitor notifications
+log commands
+!
+!debug zebra packet
+!debug zebra dplane
+!debug zebra kernel
+!
+affinity-map blue bit-position 0
+!
+interface lo
+ ip address 9.9.9.9/32
+!
+interface eth-rt3
+ ip address 10.39.0.9/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+interface eth-rt7
+ ip address 10.79.0.9/24
+ link-params
+ affinity blue
+ exit-link-params
+!
+ip forwarding
+ipv6 forwarding
+!
+line vty
+!
diff --git a/tests/topotests/isis_sr_flex_algo_topo2/test_isis_sr_flex_algo_topo2.py b/tests/topotests/isis_sr_flex_algo_topo2/test_isis_sr_flex_algo_topo2.py
new file mode 100755
index 0000000000..6a5f81def6
--- /dev/null
+++ b/tests/topotests/isis_sr_flex_algo_topo2/test_isis_sr_flex_algo_topo2.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# Part of NetDEF Topology Tests
+#
+# Copyright 2021 by LINE Corporation, Hiroki Shirokura <hiroki.shirokura@linecorp.com>
+# Copyright 2023 6WIND S.A.
+
+"""
+test_isis_sr_flex_algo_topo2.py:
+
+[+] Flex-Algos 128
+[+] Flex-Algos 129
+[+] Flex-Algos 130 include-any blue
+
+ +--------+ +--------+
+ | | | |
+ | RT1 |------------------| RT2 |
+ | | | |
+ +--------+ +--------+
+ / | \\ | \\
+ / | \\ | \\
++--------+ | \\ | \\
+| | | +--------+ | +--------+
+| RT0 | | | | | | |
+| | | | RT4 |------------------| RT3 |
++--------+ | | | | | |
+ \\ | +--------+ | +--------+
+ \\ | | | | \\
+ +--------+ | +--------+ | \\
+ | | | | | | +--------+
+ | RT5 |-------|----------| RT6 | | | |
+ | | | | | | | RT9 |
+ +--------+ | +--------+ | | |
+ \\ | \\ | +--------+
+ \\ | \\ | /
+ \\ | \\ | /
+ +--------+ +--------+
+ | | | |
+ | RT8 |------------------| RT7 |
+ | | | |
+ +--------+ +--------+
+"""
+
+import os
+import sys
+import pytest
+import json
+import time
+from functools import partial
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+
+pytestmark = [pytest.mark.isisd]
+
+
+def build_topo(tgen):
+ "Build function"
+
+ routers = []
+ for i in range(0, 10):
+ rt = tgen.add_router("rt{}".format(i))
+ rt.run("sysctl -w net.ipv4.fib_multipath_hash_policy=1")
+
+ def connect_routers(tgen, left_idx, right_idx):
+ left = "rt{}".format(left_idx)
+ right = "rt{}".format(right_idx)
+ switch = tgen.add_switch("s-{}-{}".format(left, right))
+ switch.add_link(tgen.gears[left], nodeif="eth-{}".format(right))
+ switch.add_link(tgen.gears[right], nodeif="eth-{}".format(left))
+ l_addr = "52:54:00:{}:{}:{}".format(left_idx, right_idx, left_idx)
+ tgen.gears[left].run("ip link set eth-{} down".format(right))
+ tgen.gears[left].run("ip link set eth-{} address {}".format(right, l_addr))
+ tgen.gears[left].run("ip link set eth-{} up".format(right))
+ tgen.gears[left].run("sysctl -w net.mpls.conf.eth-{}.input=1".format(right))
+ r_addr = "52:54:00:{}:{}:{}".format(left_idx, right_idx, right_idx)
+ tgen.gears[right].run("ip link set eth-{} down".format(left))
+ tgen.gears[right].run("ip link set eth-{} address {}".format(left, r_addr))
+ tgen.gears[right].run("ip link set eth-{} up".format(left))
+ tgen.gears[right].run("sysctl -w net.mpls.conf.eth-{}.input=1".format(left))
+
+ connect_routers(tgen, 0, 1)
+ connect_routers(tgen, 0, 5)
+ connect_routers(tgen, 1, 2)
+ connect_routers(tgen, 1, 4)
+ connect_routers(tgen, 1, 5)
+ connect_routers(tgen, 2, 3)
+ connect_routers(tgen, 2, 6)
+ connect_routers(tgen, 3, 4)
+ connect_routers(tgen, 3, 7)
+ connect_routers(tgen, 3, 9)
+ connect_routers(tgen, 4, 8)
+ connect_routers(tgen, 5, 6)
+ connect_routers(tgen, 5, 8)
+ connect_routers(tgen, 6, 7)
+ connect_routers(tgen, 7, 8)
+ connect_routers(tgen, 7, 9)
+
+
+def setup_module(mod):
+ "Sets up the pytest environment"
+ tgen = Topogen(build_topo, mod.__name__)
+ frrdir = tgen.config.get(tgen.CONFIG_SECTION, "frrdir")
+ if not os.path.isfile(os.path.join(frrdir, "pathd")):
+ pytest.skip("pathd daemon wasn't built")
+ tgen.start_topology()
+ router_list = tgen.routers()
+
+ # For all registered routers, load the zebra configuration file
+ for rname, router in router_list.items():
+ 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)))
+ if rname in ["rt0", "rt9"]:
+ router.load_config( TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)))
+ router.load_config( TopoRouter.RD_PATH, os.path.join(CWD, "{}/pathd.conf".format(rname)))
+ router.run("ip link add dum0 type dummy")
+ router.run("ip link set dum0 up")
+ if rname == "rt0":
+ router.run("ip addr add 10.255.0.1/24 dev dum0")
+ elif rname == "rt9":
+ router.run("ip addr add 10.255.9.1/24 dev dum0")
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ "Teardown the pytest environment"
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def setup_testcase(msg):
+ logger.info(msg)
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+ return tgen
+
+def open_json_file(filename):
+ try:
+ with open(filename, "r") as f:
+ return json.load(f)
+ except IOError:
+ assert False, "Could not read file {}".format(filename)
+
+
+def check_rib(name, cmd, expected_file):
+ def _check(name, cmd, expected_file):
+ logger.info("polling")
+ tgen = get_topogen()
+ router = tgen.gears[name]
+ output = json.loads(router.vtysh_cmd(cmd))
+ expected = open_json_file("{}/{}".format(CWD, expected_file))
+ return topotest.json_cmp(output, expected)
+
+ logger.info("[+] check {} \"{}\" {}".format(name, cmd, expected_file))
+ tgen = get_topogen()
+ func = partial(_check, name, cmd, expected_file)
+ success, result = topotest.run_and_expect(func, None, count=120, wait=0.5)
+ assert result is None, "Failed"
+
+
+def test_rib():
+ check_rib("rt0", "show mpls table json", "rt0/step1/route.json")
+ check_rib("rt1", "show mpls table json", "rt1/step1/route.json")
+ check_rib("rt2", "show mpls table json", "rt2/step1/route.json")
+ check_rib("rt3", "show mpls table json", "rt3/step1/route.json")
+ check_rib("rt4", "show mpls table json", "rt4/step1/route.json")
+ check_rib("rt5", "show mpls table json", "rt5/step1/route.json")
+ check_rib("rt6", "show mpls table json", "rt6/step1/route.json")
+ check_rib("rt7", "show mpls table json", "rt7/step1/route.json")
+ check_rib("rt8", "show mpls table json", "rt8/step1/route.json")
+ check_rib("rt9", "show mpls table json", "rt9/step1/route.json")
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/isis_te_topo1/reference/ted_step1.json b/tests/topotests/isis_te_topo1/reference/ted_step1.json
index d7711b7e59..b70626e85d 100644
--- a/tests/topotests/isis_te_topo1/reference/ted_step1.json
+++ b/tests/topotests/isis_te_topo1/reference/ted_step1.json
@@ -50,7 +50,7 @@
],
"edges":[
{
- "edge-id":65537,
+ "edge-id":"2001:db8:1::1:1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -91,7 +91,7 @@
}
},
{
- "edge-id":65538,
+ "edge-id":"2001:db8:1::1:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -132,7 +132,7 @@
}
},
{
- "edge-id":196610,
+ "edge-id":"2001:db8:3::3:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -173,7 +173,7 @@
}
},
{
- "edge-id":196611,
+ "edge-id":"2001:db8:3::3:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -215,7 +215,7 @@
}
},
{
- "edge-id":196612,
+ "edge-id":"2001:db8:5::3:4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
@@ -263,7 +263,7 @@
]
},
{
- "edge-id":262147,
+ "edge-id":"2001:db8:5::4:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -306,7 +306,7 @@
}
},
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -350,7 +350,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -391,7 +391,7 @@
}
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -432,7 +432,7 @@
}
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -473,7 +473,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -514,7 +514,7 @@
}
},
{
- "edge-id":167772931,
+ "edge-id":"10.0.3.3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -556,7 +556,7 @@
}
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -600,7 +600,7 @@
}
},
{
- "edge-id":167773188,
+ "edge-id":"10.0.4.4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
diff --git a/tests/topotests/isis_te_topo1/reference/ted_step10.json b/tests/topotests/isis_te_topo1/reference/ted_step10.json
index 7d017b3430..f029e5aab0 100644
--- a/tests/topotests/isis_te_topo1/reference/ted_step10.json
+++ b/tests/topotests/isis_te_topo1/reference/ted_step10.json
@@ -50,7 +50,7 @@
],
"edges":[
{
- "edge-id":1,
+ "edge-id":"2001:db8::1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -108,7 +108,7 @@
}
},
{
- "edge-id":2,
+ "edge-id":"2001:db8::2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -148,7 +148,7 @@
}
},
{
- "edge-id":65537,
+ "edge-id":"2001:db8:1::1:1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -189,7 +189,7 @@
}
},
{
- "edge-id":65538,
+ "edge-id":"2001:db8:1::1:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -230,7 +230,7 @@
}
},
{
- "edge-id":196610,
+ "edge-id":"2001:db8:3::3:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -271,7 +271,7 @@
}
},
{
- "edge-id":196611,
+ "edge-id":"2001:db8:3::3:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -313,7 +313,7 @@
}
},
{
- "edge-id":196612,
+ "edge-id":"2001:db8:5::3:4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
@@ -361,7 +361,7 @@
]
},
{
- "edge-id":262147,
+ "edge-id":"2001:db8:5::4:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -404,7 +404,7 @@
}
},
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -462,7 +462,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -503,7 +503,7 @@
}
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -544,7 +544,7 @@
}
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -585,7 +585,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -626,7 +626,7 @@
}
},
{
- "edge-id":167772931,
+ "edge-id":"10.0.3.3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -668,7 +668,7 @@
}
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -711,7 +711,7 @@
}
},
{
- "edge-id":167773188,
+ "edge-id":"10.0.4.4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
diff --git a/tests/topotests/isis_te_topo1/reference/ted_step2.json b/tests/topotests/isis_te_topo1/reference/ted_step2.json
index 7b2074b69c..aa2bafb15a 100644
--- a/tests/topotests/isis_te_topo1/reference/ted_step2.json
+++ b/tests/topotests/isis_te_topo1/reference/ted_step2.json
@@ -50,7 +50,7 @@
],
"edges":[
{
- "edge-id":196610,
+ "edge-id":"2001:db8:3::3:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -91,7 +91,7 @@
}
},
{
- "edge-id":196611,
+ "edge-id":"2001:db8:3::3:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -133,7 +133,7 @@
}
},
{
- "edge-id":196612,
+ "edge-id":"2001:db8:5::3:4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
@@ -181,7 +181,7 @@
]
},
{
- "edge-id":262147,
+ "edge-id":"2001:db8:5::4:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -224,7 +224,7 @@
}
},
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -268,7 +268,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -309,7 +309,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -350,7 +350,7 @@
}
},
{
- "edge-id":167772931,
+ "edge-id":"10.0.3.3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -392,7 +392,7 @@
}
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -436,7 +436,7 @@
}
},
{
- "edge-id":167773188,
+ "edge-id":"10.0.4.4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
diff --git a/tests/topotests/isis_te_topo1/reference/ted_step3.json b/tests/topotests/isis_te_topo1/reference/ted_step3.json
index 528138477a..de6d108bb3 100644
--- a/tests/topotests/isis_te_topo1/reference/ted_step3.json
+++ b/tests/topotests/isis_te_topo1/reference/ted_step3.json
@@ -50,7 +50,7 @@
],
"edges":[
{
- "edge-id":1,
+ "edge-id":"2001:db8::1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -94,7 +94,7 @@
}
},
{
- "edge-id":2,
+ "edge-id":"2001:db8::2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -134,7 +134,7 @@
}
},
{
- "edge-id":196610,
+ "edge-id":"2001:db8:3::3:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -175,7 +175,7 @@
}
},
{
- "edge-id":196611,
+ "edge-id":"2001:db8:3::3:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -217,7 +217,7 @@
}
},
{
- "edge-id":196612,
+ "edge-id":"2001:db8:5::3:4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
@@ -265,7 +265,7 @@
]
},
{
- "edge-id":262147,
+ "edge-id":"2001:db8:5::4:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -308,7 +308,7 @@
}
},
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -352,7 +352,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -393,7 +393,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -434,7 +434,7 @@
}
},
{
- "edge-id":167772931,
+ "edge-id":"10.0.3.3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -476,7 +476,7 @@
}
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -520,7 +520,7 @@
}
},
{
- "edge-id":167773188,
+ "edge-id":"10.0.4.4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
diff --git a/tests/topotests/isis_te_topo1/reference/ted_step4.json b/tests/topotests/isis_te_topo1/reference/ted_step4.json
index 528138477a..de6d108bb3 100644
--- a/tests/topotests/isis_te_topo1/reference/ted_step4.json
+++ b/tests/topotests/isis_te_topo1/reference/ted_step4.json
@@ -50,7 +50,7 @@
],
"edges":[
{
- "edge-id":1,
+ "edge-id":"2001:db8::1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -94,7 +94,7 @@
}
},
{
- "edge-id":2,
+ "edge-id":"2001:db8::2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -134,7 +134,7 @@
}
},
{
- "edge-id":196610,
+ "edge-id":"2001:db8:3::3:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -175,7 +175,7 @@
}
},
{
- "edge-id":196611,
+ "edge-id":"2001:db8:3::3:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -217,7 +217,7 @@
}
},
{
- "edge-id":196612,
+ "edge-id":"2001:db8:5::3:4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
@@ -265,7 +265,7 @@
]
},
{
- "edge-id":262147,
+ "edge-id":"2001:db8:5::4:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -308,7 +308,7 @@
}
},
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -352,7 +352,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -393,7 +393,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -434,7 +434,7 @@
}
},
{
- "edge-id":167772931,
+ "edge-id":"10.0.3.3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -476,7 +476,7 @@
}
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -520,7 +520,7 @@
}
},
{
- "edge-id":167773188,
+ "edge-id":"10.0.4.4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
diff --git a/tests/topotests/isis_te_topo1/reference/ted_step5.json b/tests/topotests/isis_te_topo1/reference/ted_step5.json
index 72e441d186..7daee99297 100644
--- a/tests/topotests/isis_te_topo1/reference/ted_step5.json
+++ b/tests/topotests/isis_te_topo1/reference/ted_step5.json
@@ -50,7 +50,7 @@
],
"edges":[
{
- "edge-id":1,
+ "edge-id":"2001:db8::1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -94,7 +94,7 @@
}
},
{
- "edge-id":2,
+ "edge-id":"2001:db8::2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -134,7 +134,7 @@
}
},
{
- "edge-id":65537,
+ "edge-id":"2001:db8:1::1:1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -175,7 +175,7 @@
}
},
{
- "edge-id":65538,
+ "edge-id":"2001:db8:1::1:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -216,7 +216,7 @@
}
},
{
- "edge-id":196610,
+ "edge-id":"2001:db8:3::3:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -257,7 +257,7 @@
}
},
{
- "edge-id":196611,
+ "edge-id":"2001:db8:3::3:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -299,7 +299,7 @@
}
},
{
- "edge-id":196612,
+ "edge-id":"2001:db8:5::3:4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
@@ -347,7 +347,7 @@
]
},
{
- "edge-id":262147,
+ "edge-id":"2001:db8:5::4:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -390,7 +390,7 @@
}
},
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -434,7 +434,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -475,7 +475,7 @@
}
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -516,7 +516,7 @@
}
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -557,7 +557,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -598,7 +598,7 @@
}
},
{
- "edge-id":167772931,
+ "edge-id":"10.0.3.3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -640,7 +640,7 @@
}
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -684,7 +684,7 @@
}
},
{
- "edge-id":167773188,
+ "edge-id":"10.0.4.4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
diff --git a/tests/topotests/isis_te_topo1/reference/ted_step6.json b/tests/topotests/isis_te_topo1/reference/ted_step6.json
index a5f50c3eba..289eb1ebc2 100644
--- a/tests/topotests/isis_te_topo1/reference/ted_step6.json
+++ b/tests/topotests/isis_te_topo1/reference/ted_step6.json
@@ -50,7 +50,7 @@
],
"edges":[
{
- "edge-id":1,
+ "edge-id":"2001:db8::1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -94,7 +94,7 @@
}
},
{
- "edge-id":2,
+ "edge-id":"2001:db8::2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -134,7 +134,7 @@
}
},
{
- "edge-id":65537,
+ "edge-id":"2001:db8:1::1:1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -175,7 +175,7 @@
}
},
{
- "edge-id":65538,
+ "edge-id":"2001:db8:1::1:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -216,7 +216,7 @@
}
},
{
- "edge-id":196610,
+ "edge-id":"2001:db8:3::3:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -257,7 +257,7 @@
}
},
{
- "edge-id":196611,
+ "edge-id":"2001:db8:3::3:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -299,7 +299,7 @@
}
},
{
- "edge-id":196612,
+ "edge-id":"2001:db8:5::3:4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
@@ -347,7 +347,7 @@
]
},
{
- "edge-id":262147,
+ "edge-id":"2001:db8:5::4:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -390,7 +390,7 @@
}
},
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -434,7 +434,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -475,7 +475,7 @@
}
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -516,7 +516,7 @@
}
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -557,7 +557,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -598,7 +598,7 @@
}
},
{
- "edge-id":167772931,
+ "edge-id":"10.0.3.3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -640,7 +640,7 @@
}
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -683,7 +683,7 @@
}
},
{
- "edge-id":167773188,
+ "edge-id":"10.0.4.4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
diff --git a/tests/topotests/isis_te_topo1/reference/ted_step7.json b/tests/topotests/isis_te_topo1/reference/ted_step7.json
index 447febce48..18eb42fd32 100644
--- a/tests/topotests/isis_te_topo1/reference/ted_step7.json
+++ b/tests/topotests/isis_te_topo1/reference/ted_step7.json
@@ -50,7 +50,7 @@
],
"edges":[
{
- "edge-id":1,
+ "edge-id":"2001:db8::1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -109,7 +109,7 @@
}
},
{
- "edge-id":2,
+ "edge-id":"2001:db8::2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -149,7 +149,7 @@
}
},
{
- "edge-id":65537,
+ "edge-id":"2001:db8:1::1:1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -190,7 +190,7 @@
}
},
{
- "edge-id":65538,
+ "edge-id":"2001:db8:1::1:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -231,7 +231,7 @@
}
},
{
- "edge-id":196610,
+ "edge-id":"2001:db8:3::3:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -272,7 +272,7 @@
}
},
{
- "edge-id":196611,
+ "edge-id":"2001:db8:3::3:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -314,7 +314,7 @@
}
},
{
- "edge-id":196612,
+ "edge-id":"2001:db8:5::3:4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
@@ -362,7 +362,7 @@
]
},
{
- "edge-id":262147,
+ "edge-id":"2001:db8:5::4:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -405,7 +405,7 @@
}
},
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -464,7 +464,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -505,7 +505,7 @@
}
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -546,7 +546,7 @@
}
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -587,7 +587,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -628,7 +628,7 @@
}
},
{
- "edge-id":167772931,
+ "edge-id":"10.0.3.3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -670,7 +670,7 @@
}
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -713,7 +713,7 @@
}
},
{
- "edge-id":167773188,
+ "edge-id":"10.0.4.4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
diff --git a/tests/topotests/isis_te_topo1/reference/ted_step8.json b/tests/topotests/isis_te_topo1/reference/ted_step8.json
index 510e034eba..ede36cf93d 100644
--- a/tests/topotests/isis_te_topo1/reference/ted_step8.json
+++ b/tests/topotests/isis_te_topo1/reference/ted_step8.json
@@ -50,7 +50,7 @@
],
"edges":[
{
- "edge-id":1,
+ "edge-id":"2001:db8::1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -109,7 +109,7 @@
}
},
{
- "edge-id":2,
+ "edge-id":"2001:db8::2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -149,7 +149,7 @@
}
},
{
- "edge-id":65537,
+ "edge-id":"2001:db8:1::1:1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -190,7 +190,7 @@
}
},
{
- "edge-id":65538,
+ "edge-id":"2001:db8:1::1:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -231,7 +231,7 @@
}
},
{
- "edge-id":196610,
+ "edge-id":"2001:db8:3::3:2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -272,7 +272,7 @@
}
},
{
- "edge-id":196611,
+ "edge-id":"2001:db8:3::3:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -314,7 +314,7 @@
}
},
{
- "edge-id":196612,
+ "edge-id":"2001:db8:5::3:4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
@@ -362,7 +362,7 @@
]
},
{
- "edge-id":262147,
+ "edge-id":"2001:db8:5::4:3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -405,7 +405,7 @@
}
},
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -464,7 +464,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -505,7 +505,7 @@
}
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0001",
@@ -546,7 +546,7 @@
}
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -587,7 +587,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -628,7 +628,7 @@
}
},
{
- "edge-id":167772931,
+ "edge-id":"10.0.3.3",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0003",
@@ -670,7 +670,7 @@
}
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0002",
@@ -713,7 +713,7 @@
}
},
{
- "edge-id":167773188,
+ "edge-id":"10.0.4.4",
"status":"Sync",
"origin":"ISIS_L2",
"advertised-router":"0000.0000.0004",
diff --git a/tests/topotests/kinds.yaml b/tests/topotests/kinds.yaml
new file mode 100644
index 0000000000..127790ed07
--- /dev/null
+++ b/tests/topotests/kinds.yaml
@@ -0,0 +1,30 @@
+version: 1
+kinds:
+ - name: frr
+ cmd: |
+ chown frr:frr -R /var/run/frr
+ chown frr:frr -R /var/log/frr
+ /usr/lib/frr/frrinit.sh start
+ tail -F /var/log/frr/frr.log
+ cleanup-cmd: |
+ /usr/lib/frr/frrinit.sh stop
+ volumes:
+ - "./%NAME%:/etc/frr"
+ - "%RUNDIR%/var.log.frr:/var/log/frr"
+ - "%RUNDIR%/var.run.frr:/var/run/frr"
+ cap-add:
+ - SYS_ADMIN
+ - AUDIT_WRITE
+ merge: ["volumes"]
+cli:
+ commands:
+ - name: ""
+ exec: "vtysh -c '{}'"
+ format: "[ROUTER ...] COMMAND"
+ help: "execute vtysh COMMAND on the router[s]"
+ kinds: ["frr"]
+ - name: "vtysh"
+ exec: "/usr/bin/vtysh"
+ format: "vtysh ROUTER [ROUTER ...]"
+ new-window: true
+ kinds: ["frr"]
diff --git a/tests/topotests/ldp_oc_acl_topo1/r1/show_ip_ospf_neighbor.json b/tests/topotests/ldp_oc_acl_topo1/r1/show_ip_ospf_neighbor.json
index 63281e9be3..c1c231de3d 100644
--- a/tests/topotests/ldp_oc_acl_topo1/r1/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_oc_acl_topo1/r1/show_ip_ospf_neighbor.json
@@ -2,9 +2,9 @@
"neighbors":{
"2.2.2.2":[
{
- "priority":2,
+ "nbrPriority":2,
"converged":"Full",
- "address":"10.0.1.2",
+ "ifaceAddress":"10.0.1.2",
"ifaceName":"r1-eth0:10.0.1.1"
}
]
diff --git a/tests/topotests/ldp_oc_acl_topo1/r2/show_ip_ospf_neighbor.json b/tests/topotests/ldp_oc_acl_topo1/r2/show_ip_ospf_neighbor.json
index f361d605ce..ee69af5e23 100644
--- a/tests/topotests/ldp_oc_acl_topo1/r2/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_oc_acl_topo1/r2/show_ip_ospf_neighbor.json
@@ -2,25 +2,25 @@
"neighbors":{
"1.1.1.1":[
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.1.1",
+ "ifaceAddress":"10.0.1.1",
"ifaceName":"r2-eth0:10.0.1.2"
}
],
"3.3.3.3":[
{
- "priority":2,
+ "nbrPriority":2,
"converged":"Full",
- "address":"10.0.2.3",
+ "ifaceAddress":"10.0.2.3",
"ifaceName":"r2-eth1:10.0.2.2"
}
],
"4.4.4.4":[
{
- "priority":3,
+ "nbrPriority":3,
"converged":"Full",
- "address":"10.0.2.4",
+ "ifaceAddress":"10.0.2.4",
"ifaceName":"r2-eth1:10.0.2.2"
}
]
diff --git a/tests/topotests/ldp_oc_acl_topo1/r3/show_ip_ospf_neighbor.json b/tests/topotests/ldp_oc_acl_topo1/r3/show_ip_ospf_neighbor.json
index 38794357ff..3f76542e94 100644
--- a/tests/topotests/ldp_oc_acl_topo1/r3/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_oc_acl_topo1/r3/show_ip_ospf_neighbor.json
@@ -2,17 +2,17 @@
"neighbors":{
"2.2.2.2":[
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.2.2",
+ "ifaceAddress":"10.0.2.2",
"ifaceName":"r3-eth0:10.0.2.3"
}
],
"4.4.4.4":[
{
- "priority":3,
+ "nbrPriority":3,
"converged":"Full",
- "address":"10.0.2.4",
+ "ifaceAddress":"10.0.2.4",
"ifaceName":"r3-eth0:10.0.2.3"
}
]
diff --git a/tests/topotests/ldp_oc_acl_topo1/r4/show_ip_ospf_neighbor.json b/tests/topotests/ldp_oc_acl_topo1/r4/show_ip_ospf_neighbor.json
index fccca693b9..5395cd25c9 100644
--- a/tests/topotests/ldp_oc_acl_topo1/r4/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_oc_acl_topo1/r4/show_ip_ospf_neighbor.json
@@ -3,17 +3,17 @@
"neighbors":{
"2.2.2.2":[
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.2.2",
+ "ifaceAddress":"10.0.2.2",
"ifaceName":"r4-eth0:10.0.2.4"
}
],
"3.3.3.3":[
{
- "priority":2,
+ "nbrPriority":2,
"converged":"Full",
- "address":"10.0.2.3",
+ "ifaceAddress":"10.0.2.3",
"ifaceName":"r4-eth0:10.0.2.4"
}
]
diff --git a/tests/topotests/ldp_oc_topo1/r1/show_ip_ospf_neighbor.json b/tests/topotests/ldp_oc_topo1/r1/show_ip_ospf_neighbor.json
index 63281e9be3..c1c231de3d 100644
--- a/tests/topotests/ldp_oc_topo1/r1/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_oc_topo1/r1/show_ip_ospf_neighbor.json
@@ -2,9 +2,9 @@
"neighbors":{
"2.2.2.2":[
{
- "priority":2,
+ "nbrPriority":2,
"converged":"Full",
- "address":"10.0.1.2",
+ "ifaceAddress":"10.0.1.2",
"ifaceName":"r1-eth0:10.0.1.1"
}
]
diff --git a/tests/topotests/ldp_oc_topo1/r2/show_ip_ospf_neighbor.json b/tests/topotests/ldp_oc_topo1/r2/show_ip_ospf_neighbor.json
index f361d605ce..ee69af5e23 100644
--- a/tests/topotests/ldp_oc_topo1/r2/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_oc_topo1/r2/show_ip_ospf_neighbor.json
@@ -2,25 +2,25 @@
"neighbors":{
"1.1.1.1":[
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.1.1",
+ "ifaceAddress":"10.0.1.1",
"ifaceName":"r2-eth0:10.0.1.2"
}
],
"3.3.3.3":[
{
- "priority":2,
+ "nbrPriority":2,
"converged":"Full",
- "address":"10.0.2.3",
+ "ifaceAddress":"10.0.2.3",
"ifaceName":"r2-eth1:10.0.2.2"
}
],
"4.4.4.4":[
{
- "priority":3,
+ "nbrPriority":3,
"converged":"Full",
- "address":"10.0.2.4",
+ "ifaceAddress":"10.0.2.4",
"ifaceName":"r2-eth1:10.0.2.2"
}
]
diff --git a/tests/topotests/ldp_oc_topo1/r3/show_ip_ospf_neighbor.json b/tests/topotests/ldp_oc_topo1/r3/show_ip_ospf_neighbor.json
index 38794357ff..3f76542e94 100644
--- a/tests/topotests/ldp_oc_topo1/r3/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_oc_topo1/r3/show_ip_ospf_neighbor.json
@@ -2,17 +2,17 @@
"neighbors":{
"2.2.2.2":[
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.2.2",
+ "ifaceAddress":"10.0.2.2",
"ifaceName":"r3-eth0:10.0.2.3"
}
],
"4.4.4.4":[
{
- "priority":3,
+ "nbrPriority":3,
"converged":"Full",
- "address":"10.0.2.4",
+ "ifaceAddress":"10.0.2.4",
"ifaceName":"r3-eth0:10.0.2.3"
}
]
diff --git a/tests/topotests/ldp_oc_topo1/r4/show_ip_ospf_neighbor.json b/tests/topotests/ldp_oc_topo1/r4/show_ip_ospf_neighbor.json
index fccca693b9..5395cd25c9 100644
--- a/tests/topotests/ldp_oc_topo1/r4/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_oc_topo1/r4/show_ip_ospf_neighbor.json
@@ -3,17 +3,17 @@
"neighbors":{
"2.2.2.2":[
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.2.2",
+ "ifaceAddress":"10.0.2.2",
"ifaceName":"r4-eth0:10.0.2.4"
}
],
"3.3.3.3":[
{
- "priority":2,
+ "nbrPriority":2,
"converged":"Full",
- "address":"10.0.2.3",
+ "ifaceAddress":"10.0.2.3",
"ifaceName":"r4-eth0:10.0.2.4"
}
]
diff --git a/tests/topotests/ldp_sync_ospf_topo1/r1/show_ip_ospf_neighbor.json b/tests/topotests/ldp_sync_ospf_topo1/r1/show_ip_ospf_neighbor.json
index 7efde22f3f..e25523d18d 100644
--- a/tests/topotests/ldp_sync_ospf_topo1/r1/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_sync_ospf_topo1/r1/show_ip_ospf_neighbor.json
@@ -2,24 +2,24 @@
"neighbors": {
"2.2.2.2": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.1.2",
+ "ifaceAddress": "10.0.1.2",
"ifaceName": "r1-eth1:10.0.1.1",
- "requestCounter": 0
+ "linkStateRequestListCounter": 0
}
],
"3.3.3.3": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.2.3",
+ "ifaceAddress": "10.0.2.3",
"ifaceName": "r1-eth2:10.0.2.1",
- "requestCounter": 0
+ "linkStateRequestListCounter": 0
}
]
}
diff --git a/tests/topotests/ldp_sync_ospf_topo1/r2/show_ip_ospf_neighbor.json b/tests/topotests/ldp_sync_ospf_topo1/r2/show_ip_ospf_neighbor.json
index 5bea193e01..fa2ea86d67 100644
--- a/tests/topotests/ldp_sync_ospf_topo1/r2/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_sync_ospf_topo1/r2/show_ip_ospf_neighbor.json
@@ -2,24 +2,24 @@
"neighbors": {
"1.1.1.1": [
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.1.1",
+ "ifaceAddress":"10.0.1.1",
"ifaceName":"r2-eth1:10.0.1.2",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
],
"3.3.3.3": [
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.3.3",
+ "ifaceAddress":"10.0.3.3",
"ifaceName":"r2-eth2:10.0.3.2",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
]
}
diff --git a/tests/topotests/ldp_sync_ospf_topo1/r3/show_ip_ospf_neighbor.json b/tests/topotests/ldp_sync_ospf_topo1/r3/show_ip_ospf_neighbor.json
index 9966297d8a..bf77e088d5 100644
--- a/tests/topotests/ldp_sync_ospf_topo1/r3/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_sync_ospf_topo1/r3/show_ip_ospf_neighbor.json
@@ -2,24 +2,24 @@
"neighbors": {
"1.1.1.1": [
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.2.1",
+ "ifaceAddress":"10.0.2.1",
"ifaceName":"r3-eth1:10.0.2.3",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
],
"2.2.2.2": [
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.3.2",
+ "ifaceAddress":"10.0.3.2",
"ifaceName":"r3-eth2:10.0.3.3",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
]
}
diff --git a/tests/topotests/ldp_topo1/r1/show_ip_ospf_neighbor.json b/tests/topotests/ldp_topo1/r1/show_ip_ospf_neighbor.json
index d9192f1104..f47c2dfad7 100644
--- a/tests/topotests/ldp_topo1/r1/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_topo1/r1/show_ip_ospf_neighbor.json
@@ -2,12 +2,12 @@
"neighbors": {
"2.2.2.2": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.1.2",
- "requestCounter": 0
+ "ifaceAddress": "10.0.1.2",
+ "linkStateRequestListCounter": 0
}
]
}
diff --git a/tests/topotests/ldp_topo1/r2/show_ip_ospf_neighbor.json b/tests/topotests/ldp_topo1/r2/show_ip_ospf_neighbor.json
index ea78592bd5..901282f876 100644
--- a/tests/topotests/ldp_topo1/r2/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_topo1/r2/show_ip_ospf_neighbor.json
@@ -2,40 +2,40 @@
"neighbors": {
"1.1.1.1": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.1.1",
- "requestCounter": 0
+ "ifaceAddress": "10.0.1.1",
+ "linkStateRequestListCounter": 0
}
],
"3.3.3.3": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.2.3",
- "requestCounter": 0
+ "ifaceAddress": "10.0.2.3",
+ "linkStateRequestListCounter": 0
},
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.3.3",
- "requestCounter": 0
+ "ifaceAddress": "10.0.3.3",
+ "linkStateRequestListCounter": 0
}
],
"4.4.4.4": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.2.4",
- "requestCounter": 0
+ "ifaceAddress": "10.0.2.4",
+ "linkStateRequestListCounter": 0
}
]
}
diff --git a/tests/topotests/ldp_topo1/r3/show_ip_ospf_neighbor.json b/tests/topotests/ldp_topo1/r3/show_ip_ospf_neighbor.json
index d3c50247ea..164040ae3e 100644
--- a/tests/topotests/ldp_topo1/r3/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_topo1/r3/show_ip_ospf_neighbor.json
@@ -2,30 +2,30 @@
"neighbors": {
"2.2.2.2": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.2.2",
- "requestCounter": 0
+ "ifaceAddress": "10.0.2.2",
+ "linkStateRequestListCounter": 0
},
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.3.2",
- "requestCounter": 0
+ "ifaceAddress": "10.0.3.2",
+ "linkStateRequestListCounter": 0
}
],
"4.4.4.4": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.2.4",
- "requestCounter": 0
+ "ifaceAddress": "10.0.2.4",
+ "linkStateRequestListCounter": 0
}
]
}
diff --git a/tests/topotests/ldp_topo1/r4/show_ip_ospf_neighbor.json b/tests/topotests/ldp_topo1/r4/show_ip_ospf_neighbor.json
index 20751a2884..98c759a6ff 100644
--- a/tests/topotests/ldp_topo1/r4/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_topo1/r4/show_ip_ospf_neighbor.json
@@ -2,22 +2,22 @@
"neighbors": {
"2.2.2.2": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.2.2",
- "requestCounter": 0
+ "ifaceAddress": "10.0.2.2",
+ "linkStateRequestListCounter": 0
}
],
"3.3.3.3": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 1,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 1,
"converged": "Full",
- "address": "10.0.2.3",
- "requestCounter": 0
+ "ifaceAddress": "10.0.2.3",
+ "linkStateRequestListCounter": 0
}
]
}
diff --git a/tests/topotests/ldp_vpls_topo1/r1/show_ip_ospf_neighbor.json b/tests/topotests/ldp_vpls_topo1/r1/show_ip_ospf_neighbor.json
index 90c8195416..9acb4f7b8c 100644
--- a/tests/topotests/ldp_vpls_topo1/r1/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_vpls_topo1/r1/show_ip_ospf_neighbor.json
@@ -2,24 +2,24 @@
"neighbors": {
"2.2.2.2": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 2,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 2,
"converged": "Full",
- "address": "10.0.1.2",
+ "ifaceAddress": "10.0.1.2",
"ifaceName": "r1-eth1:10.0.1.1",
- "requestCounter": 0
+ "linkStateRequestListCounter": 0
}
],
"3.3.3.3": [
{
- "dbSummaryCounter": 0,
- "retransmitCounter": 0,
- "priority": 2,
+ "databaseSummaryListCounter": 0,
+ "linkStateRetransmissionListCounter": 0,
+ "nbrPriority": 2,
"converged": "Full",
- "address": "10.0.2.3",
+ "ifaceAddress": "10.0.2.3",
"ifaceName": "r1-eth2:10.0.2.1",
- "requestCounter": 0
+ "linkStateRequestListCounter": 0
}
]
}
diff --git a/tests/topotests/ldp_vpls_topo1/r2/show_ip_ospf_neighbor.json b/tests/topotests/ldp_vpls_topo1/r2/show_ip_ospf_neighbor.json
index 29dde53c6d..6634199902 100644
--- a/tests/topotests/ldp_vpls_topo1/r2/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_vpls_topo1/r2/show_ip_ospf_neighbor.json
@@ -2,24 +2,24 @@
"neighbors": {
"1.1.1.1": [
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.1.1",
+ "ifaceAddress":"10.0.1.1",
"ifaceName":"r2-eth1:10.0.1.2",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
],
"3.3.3.3": [
{
- "priority":2,
+ "nbrPriority":2,
"converged":"Full",
- "address":"10.0.3.3",
+ "ifaceAddress":"10.0.3.3",
"ifaceName":"r2-eth2:10.0.3.2",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
]
}
diff --git a/tests/topotests/ldp_vpls_topo1/r3/show_ip_ospf_neighbor.json b/tests/topotests/ldp_vpls_topo1/r3/show_ip_ospf_neighbor.json
index 9966297d8a..bf77e088d5 100644
--- a/tests/topotests/ldp_vpls_topo1/r3/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ldp_vpls_topo1/r3/show_ip_ospf_neighbor.json
@@ -2,24 +2,24 @@
"neighbors": {
"1.1.1.1": [
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.2.1",
+ "ifaceAddress":"10.0.2.1",
"ifaceName":"r3-eth1:10.0.2.3",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
],
"2.2.2.2": [
{
- "priority":1,
+ "nbrPriority":1,
"converged":"Full",
- "address":"10.0.3.2",
+ "ifaceAddress":"10.0.3.2",
"ifaceName":"r3-eth2:10.0.3.3",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
]
}
diff --git a/tests/topotests/lib/common_config.py b/tests/topotests/lib/common_config.py
index e5a1e75837..d19d8db75c 100644
--- a/tests/topotests/lib/common_config.py
+++ b/tests/topotests/lib/common_config.py
@@ -492,7 +492,7 @@ def save_initial_config_on_routers(tgen):
# Get all running configs in parallel
procs = {}
for rname in router_list:
- logger.info("Fetching running config for router %s", rname)
+ logger.debug("Fetching running config for router %s", rname)
procs[rname] = router_list[rname].popen(
["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
stdin=None,
@@ -548,7 +548,7 @@ def reset_config_on_routers(tgen, routerName=None):
#
procs = {}
for rname in router_list:
- logger.info("Fetching running config for router %s", rname)
+ logger.debug("Fetching running config for router %s", rname)
procs[rname] = router_list[rname].popen(
["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
stdin=None,
@@ -570,7 +570,7 @@ def reset_config_on_routers(tgen, routerName=None):
#
procs = {}
for rname in router_list:
- logger.info(
+ logger.debug(
"Generating delta for router %s to new configuration (gen %d)", rname, gen
)
procs[rname] = tgen.net.popen(
@@ -599,7 +599,7 @@ def reset_config_on_routers(tgen, routerName=None):
#
procs = {}
for rname in router_list:
- logger.info("Applying delta config on router %s", rname)
+ logger.debug("Applying delta config on router %s", rname)
procs[rname] = router_list[rname].popen(
["/usr/bin/env", "vtysh", "-f", delta_fmt.format(rname, gen)],
@@ -611,7 +611,7 @@ def reset_config_on_routers(tgen, routerName=None):
output, _ = p.communicate()
vtysh_command = "vtysh -f {}".format(delta_fmt.format(rname, gen))
if not p.returncode:
- router_list[rname].logger.info(
+ router_list[rname].logger.debug(
'\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
vtysh_command, output
)
@@ -640,7 +640,7 @@ def reset_config_on_routers(tgen, routerName=None):
if show_router_config:
procs = {}
for rname in router_list:
- logger.info("Fetching running config for router %s", rname)
+ logger.debug("Fetching running config for router %s", rname)
procs[rname] = router_list[rname].popen(
["/usr/bin/env", "vtysh", "-c", "show running-config no-header"],
stdin=None,
@@ -657,7 +657,7 @@ def reset_config_on_routers(tgen, routerName=None):
output,
)
else:
- logger.info(
+ logger.debug(
"Configuration on router %s after reset:\n%s", rname, output
)
@@ -742,7 +742,7 @@ def load_config_to_routers(tgen, routers, save_bkup=False):
frr_cfg_bkup = frr_cfg_bkup_fmt.format(rname)
with open(frr_cfg_file, "r+") as cfg:
data = cfg.read()
- logger.info(
+ logger.debug(
"Applying following configuration on router %s (gen: %d):\n%s",
rname,
gen,
@@ -775,7 +775,7 @@ def load_config_to_routers(tgen, routers, save_bkup=False):
frr_cfg_file = frr_cfg_file_fmt.format(rname)
vtysh_command = "vtysh -f " + frr_cfg_file
if not p.returncode:
- router_list[rname].logger.info(
+ router_list[rname].logger.debug(
'\nvtysh config apply => "{}"\nvtysh output <= "{}"'.format(
vtysh_command, output
)
@@ -821,7 +821,7 @@ def load_config_to_routers(tgen, routers, save_bkup=False):
output,
)
else:
- logger.info("New configuration for router %s:\n%s", rname, output)
+ logger.debug("New configuration for router %s:\n%s", rname, output)
logger.debug("Exiting API: load_config_to_routers")
return not errors
@@ -957,10 +957,10 @@ def generate_support_bundle():
bundle_procs[rname] = tgen.net[rname].popen(gen_sup_cmd, stdin=None)
for rname, rnode in router_list.items():
- logger.info("Waiting on support bundle for %s", rname)
+ logger.debug("Waiting on support bundle for %s", rname)
output, error = bundle_procs[rname].communicate()
if output:
- logger.info(
+ logger.debug(
"Output from collecting support bundle for %s:\n%s", rname, output
)
if error:
@@ -1234,15 +1234,15 @@ def add_interfaces_to_vlan(tgen, input_dict):
cmd = "ip link add link {} name {} type vlan id {}".format(
interface, vlan_intf, vlan
)
- logger.info("[DUT: %s]: Running command: %s", dut, cmd)
+ logger.debug("[DUT: %s]: Running command: %s", dut, cmd)
result = rnode.run(cmd)
- logger.info("result %s", result)
+ logger.debug("result %s", result)
# Bringing interface up
cmd = "ip link set {} up".format(vlan_intf)
- logger.info("[DUT: %s]: Running command: %s", dut, cmd)
+ logger.debug("[DUT: %s]: Running command: %s", dut, cmd)
result = rnode.run(cmd)
- logger.info("result %s", result)
+ logger.debug("result %s", result)
# Assigning IP address
ifaddr = ipaddress.ip_interface(
@@ -1254,9 +1254,9 @@ def add_interfaces_to_vlan(tgen, input_dict):
cmd = "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
ifaddr.version, vlan_intf, ifaddr
)
- logger.info("[DUT: %s]: Running command: %s", dut, cmd)
+ logger.debug("[DUT: %s]: Running command: %s", dut, cmd)
result = rnode.run(cmd)
- logger.info("result %s", result)
+ logger.debug("result %s", result)
def tcpdump_capture_start(
@@ -1567,12 +1567,16 @@ def create_vrf_cfg(tgen, topo, input_dict=None, build=False):
vrf["name"], vrf["id"]
)
- logger.info("[DUT: %s]: Running kernel cmd [%s]", c_router, cmd)
+ logger.debug(
+ "[DUT: %s]: Running kernel cmd [%s]", c_router, cmd
+ )
rnode.run(cmd)
# Kernel cmd - Bring down VRF
cmd = "ip link set dev {} down".format(name)
- logger.info("[DUT: %s]: Running kernel cmd [%s]", c_router, cmd)
+ logger.debug(
+ "[DUT: %s]: Running kernel cmd [%s]", c_router, cmd
+ )
rnode.run(cmd)
else:
@@ -1581,14 +1585,14 @@ def create_vrf_cfg(tgen, topo, input_dict=None, build=False):
cmd = "ip link add {} type vrf table {}".format(
name, table_id
)
- logger.info(
+ logger.debug(
"[DUT: %s]: Running kernel cmd " "[%s]", c_router, cmd
)
rnode.run(cmd)
# Kernel cmd - Bring up VRF
cmd = "ip link set dev {} up".format(name)
- logger.info(
+ logger.debug(
"[DUT: %s]: Running kernel " "cmd [%s]", c_router, cmd
)
rnode.run(cmd)
@@ -1616,7 +1620,7 @@ def create_vrf_cfg(tgen, topo, input_dict=None, build=False):
interface_name, _vrf
)
- logger.info(
+ logger.debug(
"[DUT: %s]: Running" " kernel cmd [%s]",
c_router,
cmd,
@@ -1683,7 +1687,7 @@ def create_interface_in_kernel(
cmd = "ip -{0} a flush {1} scope global && ip a add {2} dev {1} && ip l set {1} up".format(
ifaddr.version, name, ifaddr
)
- logger.info("[DUT: %s]: Running command: %s", dut, cmd)
+ logger.debug("[DUT: %s]: Running command: %s", dut, cmd)
rnode.run(cmd)
if vrf:
@@ -1715,7 +1719,7 @@ def shutdown_bringup_interface_in_kernel(tgen, dut, intf_name, ifaceaction=False
action = "down"
cmd = "{} {} {}".format(cmd, intf_name, action)
- logger.info("[DUT: %s]: Running command: %s", dut, cmd)
+ logger.debug("[DUT: %s]: Running command: %s", dut, cmd)
rnode.run(cmd)
@@ -1968,7 +1972,7 @@ def retry(retry_timeout, initial_wait=0, expected=True, diag_pct=0.75):
)
if initial_wait > 0:
- logger.info("Waiting for [%s]s as initial delay", initial_wait)
+ logger.debug("Waiting for [%s]s as initial delay", initial_wait)
sleep(initial_wait)
invert_logic = not _expected
@@ -2027,13 +2031,13 @@ def retry(retry_timeout, initial_wait=0, expected=True, diag_pct=0.75):
return saved_failure
if saved_failure:
- logger.info(
+ logger.debug(
"RETRY DIAG: [failure] Sleeping %ds until next retry with %.1f retry time left - too see if timeout was too short",
retry_sleep,
seconds_left,
)
else:
- logger.info(
+ logger.debug(
"Sleeping %ds until next retry with %.1f retry time left",
retry_sleep,
seconds_left,
@@ -3357,7 +3361,19 @@ def socat_send_mld_join(
# Run socat command to send IGMP join
logger.info("[DUT: {}]: Running command: [{}]".format(server, socat_cmd))
- output = rnode.run("set +m; {} sleep 0.5".format(socat_cmd))
+ output = rnode.run("set +m; {} echo $!".format(socat_cmd))
+
+ # Check if socat join process is running
+ if output:
+ pid = output.split()[0]
+ rnode.run("touch /var/run/frr/socat_join.pid")
+ rnode.run("echo %s >> /var/run/frr/socat_join.pid" % pid)
+ else:
+ errormsg = "Socat join is not sent for {}. Error {}".format(
+ mld_group, output
+ )
+ logger.error(output)
+ return errormsg
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
return True
@@ -3415,7 +3431,7 @@ def socat_send_pim6_traffic(
if multicast_hops:
socat_cmd += "multicast-hops=255'"
- socat_cmd += " &>{}/socat.logs &".format(tgen.logdir)
+ socat_cmd += " >{}/socat.logs &".format(tgen.logdir)
# Run socat command to send pim6 traffic
logger.info(
@@ -3435,7 +3451,20 @@ def socat_send_pim6_traffic(
)
rnode.run("chmod 755 {}".format(traffic_shell_script))
- output = rnode.run("{} &> /dev/null".format(traffic_shell_script))
+ output = rnode.run("{} &>/dev/null & echo $!".format(traffic_shell_script))
+
+ # Check if socat traffic process is running
+ if output:
+ pid = output.split()[0]
+ rnode.run("touch /var/run/frr/socat_traffic.pid")
+ rnode.run("echo %s >> /var/run/frr/socat_traffic.pid" % pid)
+
+ else:
+ errormsg = "Socat traffic is not sent for {}. Error {}".format(
+ mld_group, output
+ )
+ logger.error(output)
+ return errormsg
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
return True
@@ -3465,18 +3494,30 @@ def kill_socat(tgen, dut=None, action=None):
if dut is not None and router != dut:
continue
+ traffic_shell_script = "{}/{}/traffic.sh".format(tgen.logdir, router)
+ pid_socat_join = rnode.run("cat /var/run/frr/socat_join.pid")
+ pid_socat_traffic = rnode.run("cat /var/run/frr/socat_traffic.pid")
if action == "remove_mld_join":
- cmd = "ps -ef | grep socat | grep UDP6-RECV | grep {}".format(router)
+ pids = pid_socat_join
elif action == "remove_mld_traffic":
- cmd = "ps -ef | grep socat | grep UDP6-SEND | grep {}".format(router)
+ pids = pid_socat_traffic
else:
- cmd = "ps -ef | grep socat".format(router)
-
- awk_cmd = "awk -F' ' '{print $2}' | xargs kill -9 &>/dev/null &"
- cmd = "{} | {}".format(cmd, awk_cmd)
+ pids = "\n".join([pid_socat_join, pid_socat_traffic])
- logger.debug("[DUT: {}]: Running command: [{}]".format(router, cmd))
- rnode.run(cmd)
+ if os.path.exists(traffic_shell_script):
+ cmd = (
+ "ps -ef | grep %s | awk -F' ' '{print $2}' | xargs kill -9"
+ % traffic_shell_script
+ )
+ logger.debug("[DUT: {}]: Running command: [{}]".format(router, cmd))
+ rnode.run(cmd)
+
+ for pid in pids.split("\n"):
+ pid = pid.strip()
+ if pid.isdigit():
+ cmd = "set +m; kill -9 %s &> /dev/null" % pid
+ logger.debug("[DUT: {}]: Running command: [{}]".format(router, cmd))
+ rnode.run(cmd)
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
diff --git a/tests/topotests/lib/grpc-query.py b/tests/topotests/lib/grpc-query.py
index 6457bbefdd..5dd12d581e 100755
--- a/tests/topotests/lib/grpc-query.py
+++ b/tests/topotests/lib/grpc-query.py
@@ -21,7 +21,8 @@ try:
import grpc
import grpc_tools
- from micronet import commander
+ sys.path.append(os.path.dirname(CWD))
+ from munet.base import commander
commander.cmd_raises(f"cp {CWD}/../../../grpc/frr-northbound.proto .")
commander.cmd_raises(
diff --git a/tests/topotests/lib/micronet.py b/tests/topotests/lib/micronet.py
index 1381009168..f4aa8278f1 100644
--- a/tests/topotests/lib/micronet.py
+++ b/tests/topotests/lib/micronet.py
@@ -3,1004 +3,22 @@
#
# July 9 2021, Christian Hopps <chopps@labn.net>
#
-# Copyright (c) 2021, LabN Consulting, L.L.C.
+# Copyright (c) 2021-2023, LabN Consulting, L.L.C.
#
-import datetime
-import logging
-import os
-import re
-import shlex
-import subprocess
-import sys
-import tempfile
-import time as time_mod
-import traceback
-
-root_hostname = subprocess.check_output("hostname")
-
-# This allows us to cleanup any leftovers later on
-os.environ["MICRONET_PID"] = str(os.getpid())
-
-
-class Timeout(object):
- def __init__(self, delta):
- self.started_on = datetime.datetime.now()
- self.expires_on = self.started_on + datetime.timedelta(seconds=delta)
-
- def elapsed(self):
- elapsed = datetime.datetime.now() - self.started_on
- return elapsed.total_seconds()
-
- def is_expired(self):
- return datetime.datetime.now() > self.expires_on
-
-
-def is_string(value):
- """Return True if value is a string."""
- try:
- return isinstance(value, basestring) # type: ignore
- except NameError:
- return isinstance(value, str)
-
-
-def shell_quote(command):
- """Return command wrapped in single quotes."""
- if sys.version_info[0] >= 3:
- return shlex.quote(command)
- return "'{}'".format(command.replace("'", "'\"'\"'")) # type: ignore
-
-
-def cmd_error(rc, o, e):
- s = "rc {}".format(rc)
- o = "\n\tstdout: " + o.strip() if o and o.strip() else ""
- e = "\n\tstderr: " + e.strip() if e and e.strip() else ""
- return s + o + e
-
-
-def proc_error(p, o, e):
- args = p.args if is_string(p.args) else " ".join(p.args)
- s = "rc {} pid {}\n\targs: {}".format(p.returncode, p.pid, args)
- o = "\n\tstdout: " + o.strip() if o and o.strip() else ""
- e = "\n\tstderr: " + e.strip() if e and e.strip() else ""
- return s + o + e
-
-
-def comm_error(p):
- rc = p.poll()
- assert rc is not None
- if not hasattr(p, "saved_output"):
- p.saved_output = p.communicate()
- return proc_error(p, *p.saved_output)
-
-
-class Commander(object): # pylint: disable=R0205
- """
- Commander.
-
- An object that can execute commands.
- """
-
- tmux_wait_gen = 0
-
- def __init__(self, name, logger=None):
- """Create a Commander."""
- self.name = name
- self.last = None
- self.exec_paths = {}
- self.pre_cmd = []
- self.pre_cmd_str = ""
-
- if not logger:
- self.logger = logging.getLogger(__name__ + ".commander." + name)
- else:
- self.logger = logger
-
- self.cwd = self.cmd_raises("pwd").strip()
-
- def set_logger(self, logfile):
- self.logger = logging.getLogger(__name__ + ".commander." + self.name)
- if is_string(logfile):
- handler = logging.FileHandler(logfile, mode="w")
- else:
- handler = logging.StreamHandler(logfile)
-
- fmtstr = "%(asctime)s.%(msecs)03d %(levelname)s: {}({}): %(message)s".format(
- self.__class__.__name__, self.name
- )
- handler.setFormatter(logging.Formatter(fmt=fmtstr))
- self.logger.addHandler(handler)
-
- def set_pre_cmd(self, pre_cmd=None):
- if not pre_cmd:
- self.pre_cmd = []
- self.pre_cmd_str = ""
- else:
- self.pre_cmd = pre_cmd
- self.pre_cmd_str = " ".join(self.pre_cmd) + " "
-
- def __str__(self):
- return "Commander({})".format(self.name)
-
- def get_exec_path(self, binary):
- """Return the full path to the binary executable.
-
- `binary` :: binary name or list of binary names
- """
- if is_string(binary):
- bins = [binary]
- else:
- bins = binary
- for b in bins:
- if b in self.exec_paths:
- return self.exec_paths[b]
-
- rc, output, _ = self.cmd_status("which " + b, warn=False)
- if not rc:
- return os.path.abspath(output.strip())
- return None
-
- def get_tmp_dir(self, uniq):
- return os.path.join(tempfile.mkdtemp(), uniq)
-
- def test(self, flags, arg):
- """Run test binary, with flags and arg"""
- test_path = self.get_exec_path(["test"])
- rc, output, _ = self.cmd_status([test_path, flags, arg], warn=False)
- return not rc
-
- def path_exists(self, path):
- """Check if path exists."""
- return self.test("-e", path)
-
- def _get_cmd_str(self, cmd):
- if is_string(cmd):
- return self.pre_cmd_str + cmd
- cmd = self.pre_cmd + cmd
- return " ".join(cmd)
-
- def _get_sub_args(self, cmd, defaults, **kwargs):
- if is_string(cmd):
- defaults["shell"] = True
- pre_cmd = self.pre_cmd_str
- else:
- defaults["shell"] = False
- pre_cmd = self.pre_cmd
- cmd = [str(x) for x in cmd]
- defaults.update(kwargs)
- return pre_cmd, cmd, defaults
-
- def _popen(self, method, cmd, skip_pre_cmd=False, **kwargs):
- if sys.version_info[0] >= 3:
- defaults = {
- "encoding": "utf-8",
- "stdout": subprocess.PIPE,
- "stderr": subprocess.PIPE,
- }
- else:
- defaults = {
- "stdout": subprocess.PIPE,
- "stderr": subprocess.PIPE,
- }
- pre_cmd, cmd, defaults = self._get_sub_args(cmd, defaults, **kwargs)
-
- self.logger.debug('%s: %s("%s", kwargs: %s)', self, method, cmd, defaults)
-
- actual_cmd = cmd if skip_pre_cmd else pre_cmd + cmd
- p = subprocess.Popen(actual_cmd, **defaults)
- if not hasattr(p, "args"):
- p.args = actual_cmd
- return p, actual_cmd
-
- def set_cwd(self, cwd):
- self.logger.warning("%s: 'cd' (%s) does not work outside namespaces", self, cwd)
- self.cwd = cwd
-
- def popen(self, cmd, **kwargs):
- """
- Creates a pipe with the given `command`.
-
- Args:
- command: `str` or `list` of command to open a pipe with.
- **kwargs: kwargs is eventually passed on to Popen. If `command` is a string
- then will be invoked with shell=True, otherwise `command` is a list and
- will be invoked with shell=False.
-
- Returns:
- a subprocess.Popen object.
- """
- p, _ = self._popen("popen", cmd, **kwargs)
- return p
-
- def cmd_status(self, cmd, raises=False, warn=True, stdin=None, **kwargs):
- """Execute a command."""
-
- # We are not a shell like mininet, so we need to intercept this
- chdir = False
- if not is_string(cmd):
- cmds = cmd
- else:
- # XXX we can drop this when the code stops assuming it works
- m = re.match(r"cd(\s*|\s+(\S+))$", cmd)
- if m and m.group(2):
- self.logger.warning(
- "Bad call to 'cd' (chdir) emulating, use self.set_cwd():\n%s",
- "".join(traceback.format_stack(limit=12)),
- )
- assert is_string(cmd)
- chdir = True
- cmd += " && pwd"
-
- # If we are going to run under bash then we don't need shell=True!
- cmds = ["/bin/bash", "-c", cmd]
-
- pinput = None
-
- if is_string(stdin) or isinstance(stdin, bytes):
- pinput = stdin
- stdin = subprocess.PIPE
-
- p, actual_cmd = self._popen("cmd_status", cmds, stdin=stdin, **kwargs)
- stdout, stderr = p.communicate(input=pinput)
- rc = p.wait()
-
- # For debugging purposes.
- self.last = (rc, actual_cmd, cmd, stdout, stderr)
-
- if rc:
- if warn:
- self.logger.warning(
- "%s: proc failed: %s:", self, proc_error(p, stdout, stderr)
- )
- if raises:
- # error = Exception("stderr: {}".format(stderr))
- # This annoyingly doesn't' show stderr when printed normally
- error = subprocess.CalledProcessError(rc, actual_cmd)
- error.stdout, error.stderr = stdout, stderr
- raise error
- elif chdir:
- self.set_cwd(stdout.strip())
-
- return rc, stdout, stderr
-
- def cmd_legacy(self, cmd, **kwargs):
- """Execute a command with stdout and stderr joined, *IGNORES ERROR*."""
-
- defaults = {"stderr": subprocess.STDOUT}
- defaults.update(kwargs)
- _, stdout, _ = self.cmd_status(cmd, raises=False, **defaults)
- return stdout
-
- def cmd_raises(self, cmd, **kwargs):
- """Execute a command. Raise an exception on errors"""
-
- rc, stdout, _ = self.cmd_status(cmd, raises=True, **kwargs)
- assert rc == 0
- return stdout
-
- # Run a command in a new window (gnome-terminal, screen, tmux, xterm)
- def run_in_window(
- self,
- cmd,
- wait_for=False,
- background=False,
- name=None,
- title=None,
- forcex=False,
- new_window=False,
- tmux_target=None,
- ):
- """
- Run a command in a new window (TMUX, Screen or XTerm).
-
- Args:
- wait_for: True to wait for exit from command or `str` as channel neme to signal on exit, otherwise False
- background: Do not change focus to new window.
- title: Title for new pane (tmux) or window (xterm).
- name: Name of the new window (tmux)
- forcex: Force use of X11.
- new_window: Open new window (instead of pane) in TMUX
- tmux_target: Target for tmux pane.
-
- Returns:
- the pane/window identifier from TMUX (depends on `new_window`)
- """
-
- channel = None
- if is_string(wait_for):
- channel = wait_for
- elif wait_for is True:
- channel = "{}-wait-{}".format(os.getpid(), Commander.tmux_wait_gen)
- Commander.tmux_wait_gen += 1
-
- sudo_path = self.get_exec_path(["sudo"])
- nscmd = sudo_path + " " + self.pre_cmd_str + cmd
- if "TMUX" in os.environ and not forcex:
- cmd = [self.get_exec_path("tmux")]
- if new_window:
- cmd.append("new-window")
- cmd.append("-P")
- if name:
- cmd.append("-n")
- cmd.append(name)
- if tmux_target:
- cmd.append("-t")
- cmd.append(tmux_target)
- else:
- cmd.append("split-window")
- cmd.append("-P")
- cmd.append("-h")
- if not tmux_target:
- tmux_target = os.getenv("TMUX_PANE", "")
- if background:
- cmd.append("-d")
- if tmux_target:
- cmd.append("-t")
- cmd.append(tmux_target)
- if title:
- nscmd = "printf '\033]2;{}\033\\'; {}".format(title, nscmd)
- if channel:
- nscmd = 'trap "tmux wait -S {}; exit 0" EXIT; {}'.format(channel, nscmd)
- cmd.append(nscmd)
- elif "STY" in os.environ and not forcex:
- # wait for not supported in screen for now
- channel = None
- cmd = [self.get_exec_path("screen")]
- if title:
- cmd.append("-t")
- cmd.append(title)
- if not os.path.exists(
- "/run/screen/S-{}/{}".format(os.environ["USER"], os.environ["STY"])
- ):
- cmd = ["sudo", "-u", os.environ["SUDO_USER"]] + cmd
- cmd.extend(nscmd.split(" "))
- elif "DISPLAY" in os.environ:
- # We need it broken up for xterm
- user_cmd = cmd
- cmd = [self.get_exec_path("xterm")]
- if "SUDO_USER" in os.environ:
- cmd = [self.get_exec_path("sudo"), "-u", os.environ["SUDO_USER"]] + cmd
- if title:
- cmd.append("-T")
- cmd.append(title)
- cmd.append("-e")
- cmd.append(sudo_path)
- cmd.extend(self.pre_cmd)
- cmd.extend(["bash", "-c", user_cmd])
- # if channel:
- # return self.cmd_raises(cmd, skip_pre_cmd=True)
- # else:
- p = self.popen(
- cmd,
- skip_pre_cmd=True,
- stdin=None,
- shell=False,
- )
- time_mod.sleep(2)
- if p.poll() is not None:
- self.logger.error("%s: Failed to launch xterm: %s", self, comm_error(p))
- return p
- else:
- self.logger.error(
- "DISPLAY, STY, and TMUX not in environment, can't open window"
- )
- raise Exception("Window requestd but TMUX, Screen and X11 not available")
-
- pane_info = self.cmd_raises(cmd, skip_pre_cmd=True).strip()
-
- # Re-adjust the layout
- if "TMUX" in os.environ:
- self.cmd_status(
- "tmux select-layout -t {} tiled".format(
- pane_info if not tmux_target else tmux_target
- ),
- skip_pre_cmd=True,
- )
-
- # Wait here if we weren't handed the channel to wait for
- if channel and wait_for is True:
- cmd = [self.get_exec_path("tmux"), "wait", channel]
- self.cmd_status(cmd, skip_pre_cmd=True)
-
- return pane_info
-
- def delete(self):
- pass
-
-
-class LinuxNamespace(Commander):
- """
- A linux Namespace.
-
- An object that creates and executes commands in a linux namespace
- """
-
- def __init__(
- self,
- name,
- net=True,
- mount=True,
- uts=True,
- cgroup=False,
- ipc=False,
- pid=False,
- time=False,
- user=False,
- set_hostname=True,
- private_mounts=None,
- logger=None,
- ):
- """
- Create a new linux namespace.
-
- Args:
- name: Internal name for the namespace.
- net: Create network namespace.
- mount: Create network namespace.
- uts: Create UTS (hostname) namespace.
- cgroup: Create cgroup namespace.
- ipc: Create IPC namespace.
- pid: Create PID namespace, also mounts new /proc.
- time: Create time namespace.
- user: Create user namespace, also keeps capabilities.
- set_hostname: Set the hostname to `name`, uts must also be True.
- private_mounts: List of strings of the form
- "[/external/path:]/internal/path. If no external path is specified a
- tmpfs is mounted on the internal path. Any paths specified are first
- passed to `mkdir -p`.
- logger: Passed to superclass.
- """
- super(LinuxNamespace, self).__init__(name, logger)
-
- self.logger.debug("%s: Creating", self)
-
- self.intfs = []
-
- nslist = []
- cmd = ["/usr/bin/unshare"]
- flags = ""
- self.a_flags = []
- self.ifnetns = {}
-
- if cgroup:
- nslist.append("cgroup")
- flags += "C"
- if ipc:
- nslist.append("ipc")
- flags += "i"
- if mount:
- nslist.append("mnt")
- flags += "m"
- if net:
- nslist.append("net")
- flags += "n"
- if pid:
- nslist.append("pid")
- flags += "f"
- flags += "p"
- cmd.append("--mount-proc")
- if time:
- # XXX this filename is probably wrong
- nslist.append("time")
- flags += "T"
- if user:
- nslist.append("user")
- flags += "U"
- cmd.append("--keep-caps")
- if uts:
- nslist.append("uts")
- flags += "u"
-
- if flags:
- aflags = flags.replace("f", "")
- if aflags:
- self.a_flags = ["-" + x for x in aflags]
- cmd.extend(["-" + x for x in flags])
-
- if pid:
- cmd.append(commander.get_exec_path("tini"))
- cmd.append("-vvv")
- cmd.append("/bin/cat")
-
- # Using cat and a stdin PIPE is nice as it will exit when we do. However, we
- # also detach it from the pgid so that signals do not propagate to it. This is
- # b/c it would exit early (e.g., ^C) then, at least the main micronet proc which
- # has no other processes like frr daemons running, will take the main network
- # namespace with it, which will remove the bridges and the veth pair (because
- # the bridge side veth is deleted).
- self.logger.debug("%s: creating namespace process: %s", self, cmd)
- p = subprocess.Popen(
- cmd,
- stdin=subprocess.PIPE,
- stdout=open("/dev/null", "w"),
- stderr=open("/dev/null", "w"),
- text=True,
- start_new_session=True, # detach from pgid so signals don't propagate
- shell=False,
- )
- self.p = p
- self.pid = p.pid
-
- self.logger.debug("%s: namespace pid: %d", self, self.pid)
-
- # -----------------------------------------------
- # Now let's wait until unshare completes it's job
- # -----------------------------------------------
- timeout = Timeout(30)
- while p.poll() is None and not timeout.is_expired():
- for fname in tuple(nslist):
- ours = os.readlink("/proc/self/ns/{}".format(fname))
- theirs = os.readlink("/proc/{}/ns/{}".format(self.pid, fname))
- # See if their namespace is different
- if ours != theirs:
- nslist.remove(fname)
- if not nslist:
- break
- elapsed = int(timeout.elapsed())
- if elapsed <= 3:
- time_mod.sleep(0.1)
- elif elapsed > 10:
- self.logger.warning("%s: unshare taking more than %ss", self, elapsed)
- time_mod.sleep(3)
- else:
- self.logger.info("%s: unshare taking more than %ss", self, elapsed)
- time_mod.sleep(1)
- assert p.poll() is None, "unshare unexpectedly exited!"
- assert not nslist, "unshare never unshared!"
-
- # Set pre-command based on our namespace proc
- self.base_pre_cmd = ["/usr/bin/nsenter", *self.a_flags, "-t", str(self.pid)]
- if not pid:
- self.base_pre_cmd.append("-F")
- self.set_pre_cmd(self.base_pre_cmd + ["--wd=" + self.cwd])
-
- # Remount sysfs and cgroup to pickup any changes
- self.cmd_raises("mount -t sysfs sysfs /sys")
- self.cmd_raises(
- "mount -o rw,nosuid,nodev,noexec,relatime -t cgroup2 cgroup /sys/fs/cgroup"
- )
-
- # Set the hostname to the namespace name
- if uts and set_hostname:
- # Debugging get the root hostname
- self.cmd_raises("hostname " + self.name)
- nroot = subprocess.check_output("hostname")
- if root_hostname != nroot:
- result = self.p.poll()
- assert root_hostname == nroot, "STATE of namespace process {}".format(
- result
- )
-
- if private_mounts:
- if is_string(private_mounts):
- private_mounts = [private_mounts]
- for m in private_mounts:
- s = m.split(":", 1)
- if len(s) == 1:
- self.tmpfs_mount(s[0])
- else:
- self.bind_mount(s[0], s[1])
-
- o = self.cmd_legacy("ls -l /proc/{}/ns".format(self.pid))
- self.logger.debug("namespaces:\n %s", o)
-
- # Doing this here messes up all_protocols ipv6 check
- self.cmd_raises("ip link set lo up")
-
- def __str__(self):
- return "LinuxNamespace({})".format(self.name)
-
- def tmpfs_mount(self, inner):
- self.cmd_raises("mkdir -p " + inner)
- self.cmd_raises("mount -n -t tmpfs tmpfs " + inner)
-
- def bind_mount(self, outer, inner):
- self.cmd_raises("mkdir -p " + inner)
- self.cmd_raises("mount --rbind {} {} ".format(outer, inner))
-
- def add_vlan(self, vlanname, linkiface, vlanid):
- self.logger.debug("Adding VLAN interface: %s (%s)", vlanname, vlanid)
- ip_path = self.get_exec_path("ip")
- assert ip_path, "XXX missing ip command!"
- self.cmd_raises(
- [
- ip_path,
- "link",
- "add",
- "link",
- linkiface,
- "name",
- vlanname,
- "type",
- "vlan",
- "id",
- vlanid,
- ]
- )
- self.cmd_raises([ip_path, "link", "set", "dev", vlanname, "up"])
-
- def add_loop(self, loopname):
- self.logger.debug("Adding Linux iface: %s", loopname)
- ip_path = self.get_exec_path("ip")
- assert ip_path, "XXX missing ip command!"
- self.cmd_raises([ip_path, "link", "add", loopname, "type", "dummy"])
- self.cmd_raises([ip_path, "link", "set", "dev", loopname, "up"])
-
- def add_l3vrf(self, vrfname, tableid):
- self.logger.debug("Adding Linux VRF: %s", vrfname)
- ip_path = self.get_exec_path("ip")
- assert ip_path, "XXX missing ip command!"
- self.cmd_raises(
- [ip_path, "link", "add", vrfname, "type", "vrf", "table", tableid]
- )
- self.cmd_raises([ip_path, "link", "set", "dev", vrfname, "up"])
-
- def del_iface(self, iface):
- self.logger.debug("Removing Linux Iface: %s", iface)
- ip_path = self.get_exec_path("ip")
- assert ip_path, "XXX missing ip command!"
- self.cmd_raises([ip_path, "link", "del", iface])
-
- def attach_iface_to_l3vrf(self, ifacename, vrfname):
- self.logger.debug("Attaching Iface %s to Linux VRF %s", ifacename, vrfname)
- ip_path = self.get_exec_path("ip")
- assert ip_path, "XXX missing ip command!"
- if vrfname:
- self.cmd_raises(
- [ip_path, "link", "set", "dev", ifacename, "master", vrfname]
- )
- else:
- self.cmd_raises([ip_path, "link", "set", "dev", ifacename, "nomaster"])
-
- def add_netns(self, ns):
- self.logger.debug("Adding network namespace %s", ns)
-
- ip_path = self.get_exec_path("ip")
- assert ip_path, "XXX missing ip command!"
- if os.path.exists("/run/netns/{}".format(ns)):
- self.logger.warning("%s: Removing existing nsspace %s", self, ns)
- try:
- self.delete_netns(ns)
- except Exception as ex:
- self.logger.warning(
- "%s: Couldn't remove existing nsspace %s: %s",
- self,
- ns,
- str(ex),
- exc_info=True,
- )
- self.cmd_raises([ip_path, "netns", "add", ns])
-
- def delete_netns(self, ns):
- self.logger.debug("Deleting network namespace %s", ns)
-
- ip_path = self.get_exec_path("ip")
- assert ip_path, "XXX missing ip command!"
- self.cmd_raises([ip_path, "netns", "delete", ns])
-
- def set_intf_netns(self, intf, ns, up=False):
- # In case a user hard-codes 1 thinking it "resets"
- ns = str(ns)
- if ns == "1":
- ns = str(self.pid)
-
- self.logger.debug("Moving interface %s to namespace %s", intf, ns)
-
- cmd = "ip link set {} netns " + ns
- if up:
- cmd += " up"
- self.intf_ip_cmd(intf, cmd)
- if ns == str(self.pid):
- # If we are returning then remove from dict
- if intf in self.ifnetns:
- del self.ifnetns[intf]
- else:
- self.ifnetns[intf] = ns
-
- def reset_intf_netns(self, intf):
- self.logger.debug("Moving interface %s to default namespace", intf)
- self.set_intf_netns(intf, str(self.pid))
-
- def intf_ip_cmd(self, intf, cmd):
- """Run an ip command for considering an interfaces possible namespace.
-
- `cmd` - format is run using the interface name on the command
- """
- if intf in self.ifnetns:
- assert cmd.startswith("ip ")
- cmd = "ip -n " + self.ifnetns[intf] + cmd[2:]
- self.cmd_raises(cmd.format(intf))
-
- def set_cwd(self, cwd):
- # Set pre-command based on our namespace proc
- self.logger.debug("%s: new CWD %s", self, cwd)
- self.set_pre_cmd(self.base_pre_cmd + ["--wd=" + cwd])
-
- def register_interface(self, ifname):
- if ifname not in self.intfs:
- self.intfs.append(ifname)
-
- def delete(self):
- if self.p and self.p.poll() is None:
- if sys.version_info[0] >= 3:
- try:
- self.p.terminate()
- self.p.communicate(timeout=10)
- except subprocess.TimeoutExpired:
- self.p.kill()
- self.p.communicate(timeout=2)
- else:
- self.p.kill()
- self.p.communicate()
- self.set_pre_cmd(["/bin/false"])
-
-
-class SharedNamespace(Commander):
- """
- Share another namespace.
-
- An object that executes commands in an existing pid's linux namespace
- """
-
- def __init__(self, name, pid, aflags=("-a",), logger=None):
- """
- Share a linux namespace.
-
- Args:
- name: Internal name for the namespace.
- pid: PID of the process to share with.
- """
- super(SharedNamespace, self).__init__(name, logger)
-
- self.logger.debug("%s: Creating", self)
-
- self.pid = pid
- self.intfs = []
- self.a_flags = aflags
-
- # Set pre-command based on our namespace proc
- self.set_pre_cmd(
- ["/usr/bin/nsenter", *self.a_flags, "-t", str(self.pid), "--wd=" + self.cwd]
- )
-
- def __str__(self):
- return "SharedNamespace({})".format(self.name)
-
- def set_cwd(self, cwd):
- # Set pre-command based on our namespace proc
- self.logger.debug("%s: new CWD %s", self, cwd)
- self.set_pre_cmd(
- ["/usr/bin/nsenter", *self.a_flags, "-t", str(self.pid), "--wd=" + cwd]
- )
-
- def register_interface(self, ifname):
- if ifname not in self.intfs:
- self.intfs.append(ifname)
-
-
-class Bridge(SharedNamespace):
- """
- A linux bridge.
- """
-
- next_brid_ord = 0
-
- @classmethod
- def _get_next_brid(cls):
- brid_ord = cls.next_brid_ord
- cls.next_brid_ord += 1
- return brid_ord
-
- def __init__(self, name=None, unet=None, logger=None):
- """Create a linux Bridge."""
-
- self.unet = unet
- self.brid_ord = self._get_next_brid()
- if name:
- self.brid = name
- else:
- self.brid = "br{}".format(self.brid_ord)
- name = self.brid
-
- super(Bridge, self).__init__(name, unet.pid, aflags=unet.a_flags, logger=logger)
-
- self.logger.debug("Bridge: Creating")
-
- assert len(self.brid) <= 16 # Make sure fits in IFNAMSIZE
- self.cmd_raises("ip link delete {} || true".format(self.brid))
- self.cmd_raises("ip link add {} type bridge".format(self.brid))
- self.cmd_raises("ip link set {} up".format(self.brid))
-
- self.logger.debug("%s: Created, Running", self)
-
- def __str__(self):
- return "Bridge({})".format(self.brid)
-
- def delete(self):
- """Stop the bridge (i.e., delete the linux resources)."""
-
- rc, o, e = self.cmd_status("ip link show {}".format(self.brid), warn=False)
- if not rc:
- rc, o, e = self.cmd_status(
- "ip link delete {}".format(self.brid), warn=False
- )
- if rc:
- self.logger.error(
- "%s: error deleting bridge %s: %s",
- self,
- self.brid,
- cmd_error(rc, o, e),
- )
- else:
- self.logger.debug("%s: Deleted.", self)
-
-
-class Micronet(LinuxNamespace): # pylint: disable=R0205
- """
- Micronet.
- """
-
- def __init__(self):
- """Create a Micronet."""
-
- self.hosts = {}
- self.switches = {}
- self.links = {}
- self.macs = {}
- self.rmacs = {}
-
- super(Micronet, self).__init__("micronet", mount=True, net=True, uts=True)
-
- self.logger.debug("%s: Creating", self)
-
- def __str__(self):
- return "Micronet()"
-
- def __getitem__(self, key):
- if key in self.switches:
- return self.switches[key]
- return self.hosts[key]
-
- def add_host(self, name, cls=LinuxNamespace, **kwargs):
- """Add a host to micronet."""
-
- self.logger.debug("%s: add_host %s", self, name)
-
- self.hosts[name] = cls(name, **kwargs)
- # Create a new mounted FS for tracking nested network namespaces creatd by the
- # user with `ip netns add`
- self.hosts[name].tmpfs_mount("/run/netns")
-
- def add_link(self, name1, name2, if1, if2):
- """Add a link between switch and host to micronet."""
- isp2p = False
- if name1 in self.switches:
- assert name2 in self.hosts
- elif name2 in self.switches:
- assert name1 in self.hosts
- name1, name2 = name2, name1
- if1, if2 = if2, if1
- else:
- # p2p link
- assert name1 in self.hosts
- assert name2 in self.hosts
- isp2p = True
-
- lname = "{}:{}-{}:{}".format(name1, if1, name2, if2)
- self.logger.debug("%s: add_link %s%s", self, lname, " p2p" if isp2p else "")
- self.links[lname] = (name1, if1, name2, if2)
-
- # And create the veth now.
- if isp2p:
- lhost, rhost = self.hosts[name1], self.hosts[name2]
- lifname = "i1{:x}".format(lhost.pid)
- rifname = "i2{:x}".format(rhost.pid)
- self.cmd_raises(
- "ip link add {} type veth peer name {}".format(lifname, rifname)
- )
-
- self.cmd_raises("ip link set {} netns {}".format(lifname, lhost.pid))
- lhost.cmd_raises("ip link set {} name {}".format(lifname, if1))
- lhost.cmd_raises("ip link set {} up".format(if1))
- lhost.register_interface(if1)
-
- self.cmd_raises("ip link set {} netns {}".format(rifname, rhost.pid))
- rhost.cmd_raises("ip link set {} name {}".format(rifname, if2))
- rhost.cmd_raises("ip link set {} up".format(if2))
- rhost.register_interface(if2)
- else:
- switch = self.switches[name1]
- host = self.hosts[name2]
-
- assert len(if1) <= 16 and len(if2) <= 16 # Make sure fits in IFNAMSIZE
-
- self.logger.debug("%s: Creating veth pair for link %s", self, lname)
- self.cmd_raises(
- "ip link add {} type veth peer name {} netns {}".format(
- if1, if2, host.pid
- )
- )
- self.cmd_raises("ip link set {} netns {}".format(if1, switch.pid))
- switch.register_interface(if1)
- host.register_interface(if2)
- self.cmd_raises("ip link set {} master {}".format(if1, switch.brid))
- self.cmd_raises("ip link set {} up".format(if1))
- host.cmd_raises("ip link set {} up".format(if2))
-
- # Cache the MAC values, and reverse mapping
- self.get_mac(name1, if1)
- self.get_mac(name2, if2)
-
- def add_switch(self, name):
- """Add a switch to micronet."""
-
- self.logger.debug("%s: add_switch %s", self, name)
- self.switches[name] = Bridge(name, self)
-
- def get_mac(self, name, ifname):
- if name in self.hosts:
- dev = self.hosts[name]
- else:
- dev = self.switches[name]
-
- if (name, ifname) not in self.macs:
- _, output, _ = dev.cmd_status("ip -o link show " + ifname)
- m = re.match(".*link/(loopback|ether) ([0-9a-fA-F:]+) .*", output)
- mac = m.group(2)
- self.macs[(name, ifname)] = mac
- self.rmacs[mac] = (name, ifname)
-
- return self.macs[(name, ifname)]
-
- def delete(self):
- """Delete the micronet topology."""
-
- self.logger.debug("%s: Deleting.", self)
-
- for lname, (_, _, rname, rif) in self.links.items():
- host = self.hosts[rname]
-
- self.logger.debug("%s: Deleting veth pair for link %s", self, lname)
-
- rc, o, e = host.cmd_status("ip link delete {}".format(rif), warn=False)
- if rc:
- self.logger.error(
- "Error deleting veth pair %s: %s", lname, cmd_error(rc, o, e)
- )
-
- self.links = {}
-
- for host in self.hosts.values():
- try:
- host.delete()
- except Exception as error:
- self.logger.error(
- "%s: error while deleting host %s: %s", self, host, error
- )
-
- self.hosts = {}
-
- for switch in self.switches.values():
- try:
- switch.delete()
- except Exception as error:
- self.logger.error(
- "%s: error while deleting switch %s: %s", self, switch, error
- )
- self.switches = {}
-
- self.logger.debug("%s: Deleted.", self)
-
- super(Micronet, self).delete()
-
-
-# ---------------------------
-# Root level utility function
-# ---------------------------
-
-
-def get_exec_path(binary):
- base = Commander("base")
- return base.get_exec_path(binary)
-
-
-commander = Commander("micronet")
+# flake8: noqa
+
+from munet.base import BaseMunet as Micronet
+from munet.base import (
+ Bridge,
+ Commander,
+ LinuxNamespace,
+ SharedNamespace,
+ Timeout,
+ cmd_error,
+ comm_error,
+ commander,
+ get_exec_path,
+ proc_error,
+ root_hostname,
+ shell_quote,
+)
diff --git a/tests/topotests/lib/micronet_cli.py b/tests/topotests/lib/micronet_cli.py
deleted file mode 100644
index e54b75f710..0000000000
--- a/tests/topotests/lib/micronet_cli.py
+++ /dev/null
@@ -1,306 +0,0 @@
-# -*- coding: utf-8 eval: (blacken-mode 1) -*-
-# SPDX-License-Identifier: GPL-2.0-or-later
-#
-# July 24 2021, Christian Hopps <chopps@labn.net>
-#
-# Copyright (c) 2021, LabN Consulting, L.L.C.
-#
-import argparse
-import logging
-import os
-import pty
-import re
-import readline
-import select
-import socket
-import subprocess
-import sys
-import tempfile
-import termios
-import tty
-
-
-ENDMARKER = b"\x00END\x00"
-
-
-def lineiter(sock):
- s = ""
- while True:
- sb = sock.recv(256)
- if not sb:
- return
-
- s += sb.decode("utf-8")
- i = s.find("\n")
- if i != -1:
- yield s[:i]
- s = s[i + 1 :]
-
-
-def spawn(unet, host, cmd):
- if sys.stdin.isatty():
- old_tty = termios.tcgetattr(sys.stdin)
- tty.setraw(sys.stdin.fileno())
- try:
- master_fd, slave_fd = pty.openpty()
-
- # use os.setsid() make it run in a new process group, or bash job
- # control will not be enabled
- p = unet.hosts[host].popen(
- cmd,
- preexec_fn=os.setsid,
- stdin=slave_fd,
- stdout=slave_fd,
- stderr=slave_fd,
- universal_newlines=True,
- )
-
- while p.poll() is None:
- r, w, e = select.select([sys.stdin, master_fd], [], [], 0.25)
- if sys.stdin in r:
- d = os.read(sys.stdin.fileno(), 10240)
- os.write(master_fd, d)
- elif master_fd in r:
- o = os.read(master_fd, 10240)
- if o:
- os.write(sys.stdout.fileno(), o)
- finally:
- # restore tty settings back
- if sys.stdin.isatty():
- termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
-
-
-def doline(unet, line, writef):
- def host_cmd_split(unet, cmd):
- csplit = cmd.split()
- for i, e in enumerate(csplit):
- if e not in unet.hosts:
- break
- hosts = csplit[:i]
- if not hosts:
- hosts = sorted(unet.hosts.keys())
- cmd = " ".join(csplit[i:])
- return hosts, cmd
-
- line = line.strip()
- m = re.match(r"^(\S+)(?:\s+(.*))?$", line)
- if not m:
- return True
-
- cmd = m.group(1)
- oargs = m.group(2) if m.group(2) else ""
- if cmd == "q" or cmd == "quit":
- return False
- if cmd == "hosts":
- writef("%% hosts: %s\n" % " ".join(sorted(unet.hosts.keys())))
- elif cmd in ["term", "vtysh", "xterm"]:
- args = oargs.split()
- if not args or (len(args) == 1 and args[0] == "*"):
- args = sorted(unet.hosts.keys())
- hosts = [unet.hosts[x] for x in args if x in unet.hosts]
- for host in hosts:
- if cmd == "t" or cmd == "term":
- host.run_in_window("bash", title="sh-%s" % host)
- elif cmd == "v" or cmd == "vtysh":
- host.run_in_window("vtysh", title="vt-%s" % host)
- elif cmd == "x" or cmd == "xterm":
- host.run_in_window("bash", title="sh-%s" % host, forcex=True)
- elif cmd == "sh":
- hosts, cmd = host_cmd_split(unet, oargs)
- for host in hosts:
- if sys.stdin.isatty():
- spawn(unet, host, cmd)
- else:
- if len(hosts) > 1:
- writef("------ Host: %s ------\n" % host)
- output = unet.hosts[host].cmd_legacy(cmd)
- writef(output)
- if len(hosts) > 1:
- writef("------- End: %s ------\n" % host)
- writef("\n")
- elif cmd == "h" or cmd == "help":
- writef(
- """
-Commands:
- help :: this help
- sh [hosts] <shell-command> :: execute <shell-command> on <host>
- term [hosts] :: open shell terminals for hosts
- vtysh [hosts] :: open vtysh terminals for hosts
- [hosts] <vtysh-command> :: execute vtysh-command on hosts\n\n"""
- )
- else:
- hosts, cmd = host_cmd_split(unet, line)
- for host in hosts:
- if len(hosts) > 1:
- writef("------ Host: %s ------\n" % host)
- output = unet.hosts[host].cmd_legacy('vtysh -c "{}"'.format(cmd))
- writef(output)
- if len(hosts) > 1:
- writef("------- End: %s ------\n" % host)
- writef("\n")
- return True
-
-
-def cli_server_setup(unet):
- sockdir = tempfile.mkdtemp("-sockdir", "pyt")
- sockpath = os.path.join(sockdir, "cli-server.sock")
- try:
- sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- sock.settimeout(10)
- sock.bind(sockpath)
- sock.listen(1)
- return sock, sockdir, sockpath
- except Exception:
- unet.cmd_status("rm -rf " + sockdir)
- raise
-
-
-def cli_server(unet, server_sock):
- sock, addr = server_sock.accept()
-
- # Go into full non-blocking mode now
- sock.settimeout(None)
-
- for line in lineiter(sock):
- line = line.strip()
-
- def writef(x):
- xb = x.encode("utf-8")
- sock.send(xb)
-
- if not doline(unet, line, writef):
- return
- sock.send(ENDMARKER)
-
-
-def cli_client(sockpath, prompt="unet> "):
- sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- sock.settimeout(10)
- sock.connect(sockpath)
-
- # Go into full non-blocking mode now
- sock.settimeout(None)
-
- print("\n--- Micronet CLI Starting ---\n\n")
- while True:
- if sys.version_info[0] == 2:
- line = raw_input(prompt) # pylint: disable=E0602
- else:
- line = input(prompt)
- if line is None:
- return
-
- # Need to put \n back
- line += "\n"
-
- # Send the CLI command
- sock.send(line.encode("utf-8"))
-
- def bendswith(b, sentinel):
- slen = len(sentinel)
- return len(b) >= slen and b[-slen:] == sentinel
-
- # Collect the output
- rb = b""
- while not bendswith(rb, ENDMARKER):
- lb = sock.recv(4096)
- if not lb:
- return
- rb += lb
-
- # Remove the marker
- rb = rb[: -len(ENDMARKER)]
-
- # Write the output
- sys.stdout.write(rb.decode("utf-8"))
-
-
-def local_cli(unet, outf, prompt="unet> "):
- print("\n--- Micronet CLI Starting ---\n\n")
- while True:
- if sys.version_info[0] == 2:
- line = raw_input(prompt) # pylint: disable=E0602
- else:
- line = input(prompt)
- if line is None:
- return
- if not doline(unet, line, outf.write):
- return
-
-
-def cli(
- unet,
- histfile=None,
- sockpath=None,
- force_window=False,
- title=None,
- prompt=None,
- background=True,
-):
- logger = logging.getLogger("cli-client")
-
- if prompt is None:
- prompt = "unet> "
-
- if force_window or not sys.stdin.isatty():
- # Run CLI in another window b/c we have no tty.
- sock, sockdir, sockpath = cli_server_setup(unet)
-
- python_path = unet.get_exec_path(["python3", "python"])
- us = os.path.realpath(__file__)
- cmd = "{} {}".format(python_path, us)
- if histfile:
- cmd += " --histfile=" + histfile
- if title:
- cmd += " --prompt={}".format(title)
- cmd += " " + sockpath
-
- try:
- unet.run_in_window(cmd, new_window=True, title=title, background=background)
- return cli_server(unet, sock)
- finally:
- unet.cmd_status("rm -rf " + sockdir)
-
- if not unet:
- logger.debug("client-cli using sockpath %s", sockpath)
-
- try:
- if histfile is None:
- histfile = os.path.expanduser("~/.micronet-history.txt")
- if not os.path.exists(histfile):
- if unet:
- unet.cmd("touch " + histfile)
- else:
- subprocess.run("touch " + histfile)
- if histfile:
- readline.read_history_file(histfile)
- except Exception:
- pass
-
- try:
- if sockpath:
- cli_client(sockpath, prompt=prompt)
- else:
- local_cli(unet, sys.stdout, prompt=prompt)
- except EOFError:
- pass
- except Exception as ex:
- logger.critical("cli: got exception: %s", ex, exc_info=True)
- raise
- finally:
- readline.write_history_file(histfile)
-
-
-if __name__ == "__main__":
- logging.basicConfig(level=logging.DEBUG, filename="/tmp/topotests/cli-client.log")
- logger = logging.getLogger("cli-client")
- logger.info("Start logging cli-client")
-
- parser = argparse.ArgumentParser()
- parser.add_argument("--histfile", help="file to user for history")
- parser.add_argument("--prompt-text", help="prompt string to use")
- parser.add_argument("socket", help="path to pair of sockets to communicate over")
- args = parser.parse_args()
-
- prompt = "{}> ".format(args.prompt_text) if args.prompt_text else "unet> "
- cli(None, args.histfile, args.socket, prompt=prompt)
diff --git a/tests/topotests/lib/micronet_compat.py b/tests/topotests/lib/micronet_compat.py
index 5a69c56d8d..c5c2adc545 100644
--- a/tests/topotests/lib/micronet_compat.py
+++ b/tests/topotests/lib/micronet_compat.py
@@ -3,140 +3,44 @@
#
# July 11 2021, Christian Hopps <chopps@labn.net>
#
-# Copyright (c) 2021, LabN Consulting, L.L.C
+# Copyright (c) 2021-2023, LabN Consulting, L.L.C
#
-
-import glob
-import logging
+import ipaddress
import os
-import signal
-import time
-
-from lib.micronet import LinuxNamespace, Micronet
-from lib.micronet_cli import cli
-
-
-def get_pids_with_env(has_var, has_val=None):
- result = {}
- for pidenv in glob.iglob("/proc/*/environ"):
- pid = pidenv.split("/")[2]
- try:
- with open(pidenv, "rb") as rfb:
- envlist = [
- x.decode("utf-8").split("=", 1) for x in rfb.read().split(b"\0")
- ]
- envlist = [[x[0], ""] if len(x) == 1 else x for x in envlist]
- envdict = dict(envlist)
- if has_var not in envdict:
- continue
- if has_val is None:
- result[pid] = envdict
- elif envdict[has_var] == str(has_val):
- result[pid] = envdict
- except Exception:
- # E.g., process exited and files are gone
- pass
- return result
-
-
-def _kill_piddict(pids_by_upid, sig):
- for upid, pids in pids_by_upid:
- logging.info(
- "Sending %s to (%s) of micronet pid %s", sig, ", ".join(pids), upid
- )
- for pid in pids:
- try:
- os.kill(int(pid), sig)
- except Exception:
- pass
-
-
-def _get_our_pids():
- ourpid = str(os.getpid())
- piddict = get_pids_with_env("MICRONET_PID", ourpid)
- pids = [x for x in piddict if x != ourpid]
- if pids:
- return {ourpid: pids}
- return {}
-
-
-def _get_other_pids():
- piddict = get_pids_with_env("MICRONET_PID")
- unet_pids = {d["MICRONET_PID"] for d in piddict.values()}
- pids_by_upid = {p: set() for p in unet_pids}
- for pid, envdict in piddict.items():
- pids_by_upid[envdict["MICRONET_PID"]].add(pid)
- # Filter out any child pid sets whos micronet pid is still running
- return {x: y for x, y in pids_by_upid.items() if x not in y}
-
-
-def _get_pids_by_upid(ours):
- if ours:
- return _get_our_pids()
- return _get_other_pids()
-
-
-def _cleanup_pids(ours):
- pids_by_upid = _get_pids_by_upid(ours).items()
- if not pids_by_upid:
- return
-
- _kill_piddict(pids_by_upid, signal.SIGTERM)
-
- # Give them 5 second to exit cleanly
- logging.info("Waiting up to 5s to allow for clean exit of abandon'd pids")
- for _ in range(0, 5):
- pids_by_upid = _get_pids_by_upid(ours).items()
- if not pids_by_upid:
- return
- time.sleep(1)
-
- pids_by_upid = _get_pids_by_upid(ours).items()
- _kill_piddict(pids_by_upid, signal.SIGKILL)
-
-
-def cleanup_current():
- """Attempt to cleanup preview runs.
-
- Currently this only scans for old processes.
- """
- logging.info("reaping current micronet processes")
- _cleanup_pids(True)
-
-def cleanup_previous():
- """Attempt to cleanup preview runs.
-
- Currently this only scans for old processes.
- """
- logging.info("reaping past micronet processes")
- _cleanup_pids(False)
+from munet import cli
+from munet.base import BaseMunet, LinuxNamespace
class Node(LinuxNamespace):
"""Node (mininet compat)."""
- def __init__(self, name, **kwargs):
- """
- Create a Node.
- """
- self.params = kwargs
+ def __init__(self, name, rundir=None, **kwargs):
+ nkwargs = {}
+ if "unet" in kwargs:
+ nkwargs["unet"] = kwargs["unet"]
if "private_mounts" in kwargs:
- private_mounts = kwargs["private_mounts"]
- else:
- private_mounts = kwargs.get("privateDirs", [])
+ nkwargs["private_mounts"] = kwargs["private_mounts"]
+ if "logger" in kwargs:
+ nkwargs["logger"] = kwargs["logger"]
- logger = kwargs.get("logger")
+ # This is expected by newer munet CLI code
+ self.config_dirname = ""
+ self.config = {"kind": "frr"}
+ self.mgmt_ip = None
+ self.mgmt_ip6 = None
- super(Node, self).__init__(name, logger=logger, private_mounts=private_mounts)
+ super().__init__(name, **nkwargs)
+
+ self.rundir = self.unet.rundir.joinpath(self.name)
def cmd(self, cmd, **kwargs):
"""Execute a command, joins stdout, stderr, ignores exit status."""
return super(Node, self).cmd_legacy(cmd, **kwargs)
- def config(self, lo="up", **params):
+ def config_host(self, lo="up", **params):
"""Called by Micronet when topology is built (but not started)."""
# mininet brings up loopback here.
del params
@@ -148,25 +52,79 @@ class Node(LinuxNamespace):
def terminate(self):
return
+ def add_vlan(self, vlanname, linkiface, vlanid):
+ self.logger.debug("Adding VLAN interface: %s (%s)", vlanname, vlanid)
+ ip_path = self.get_exec_path("ip")
+ assert ip_path, "XXX missing ip command!"
+ self.cmd_raises(
+ [
+ ip_path,
+ "link",
+ "add",
+ "link",
+ linkiface,
+ "name",
+ vlanname,
+ "type",
+ "vlan",
+ "id",
+ vlanid,
+ ]
+ )
+ self.cmd_raises([ip_path, "link", "set", "dev", vlanname, "up"])
+
+ def add_loop(self, loopname):
+ self.logger.debug("Adding Linux iface: %s", loopname)
+ ip_path = self.get_exec_path("ip")
+ assert ip_path, "XXX missing ip command!"
+ self.cmd_raises([ip_path, "link", "add", loopname, "type", "dummy"])
+ self.cmd_raises([ip_path, "link", "set", "dev", loopname, "up"])
+
+ def add_l3vrf(self, vrfname, tableid):
+ self.logger.debug("Adding Linux VRF: %s", vrfname)
+ ip_path = self.get_exec_path("ip")
+ assert ip_path, "XXX missing ip command!"
+ self.cmd_raises(
+ [ip_path, "link", "add", vrfname, "type", "vrf", "table", tableid]
+ )
+ self.cmd_raises([ip_path, "link", "set", "dev", vrfname, "up"])
+
+ def del_iface(self, iface):
+ self.logger.debug("Removing Linux Iface: %s", iface)
+ ip_path = self.get_exec_path("ip")
+ assert ip_path, "XXX missing ip command!"
+ self.cmd_raises([ip_path, "link", "del", iface])
+
+ def attach_iface_to_l3vrf(self, ifacename, vrfname):
+ self.logger.debug("Attaching Iface %s to Linux VRF %s", ifacename, vrfname)
+ ip_path = self.get_exec_path("ip")
+ assert ip_path, "XXX missing ip command!"
+ if vrfname:
+ self.cmd_raises(
+ [ip_path, "link", "set", "dev", ifacename, "master", vrfname]
+ )
+ else:
+ self.cmd_raises([ip_path, "link", "set", "dev", ifacename, "nomaster"])
+
+ set_cwd = LinuxNamespace.set_ns_cwd
+
class Topo(object): # pylint: disable=R0205
def __init__(self, *args, **kwargs):
raise Exception("Remove Me")
-class Mininet(Micronet):
+class Mininet(BaseMunet):
"""
Mininet using Micronet.
"""
g_mnet_inst = None
- def __init__(self, controller=None):
+ def __init__(self, rundir=None, pytestconfig=None):
"""
Create a Micronet.
"""
- assert not controller
-
if Mininet.g_mnet_inst is not None:
Mininet.g_mnet_inst.stop()
Mininet.g_mnet_inst = self
@@ -181,7 +139,145 @@ class Mininet(Micronet):
# to set permissions to root:frr 770 to make this unneeded in that case
# os.umask(0)
- super(Mininet, self).__init__()
+ super(Mininet, self).__init__(
+ pid=False, rundir=rundir, pytestconfig=pytestconfig
+ )
+
+ # From munet/munet/native.py
+ with open(os.path.join(self.rundir, "nspid"), "w", encoding="ascii") as f:
+ f.write(f"{self.pid}\n")
+
+ with open(os.path.join(self.rundir, "nspids"), "w", encoding="ascii") as f:
+ f.write(f'{" ".join([str(x) for x in self.pids])}\n')
+
+ hosts_file = os.path.join(self.rundir, "hosts.txt")
+ with open(hosts_file, "w", encoding="ascii") as hf:
+ hf.write(
+ f"""127.0.0.1\tlocalhost {self.name}
+::1\tip6-localhost ip6-loopback
+fe00::0\tip6-localnet
+ff00::0\tip6-mcastprefix
+ff02::1\tip6-allnodes
+ff02::2\tip6-allrouters
+"""
+ )
+ self.bind_mount(hosts_file, "/etc/hosts")
+
+ # Common CLI commands for any topology
+ cdict = {
+ "commands": [
+ #
+ # Window commands.
+ #
+ {
+ "name": "pcap",
+ "format": "pcap NETWORK",
+ "help": (
+ "capture packets from NETWORK into file capture-NETWORK.pcap"
+ " the command is run within a new window which also shows"
+ " packet summaries. NETWORK can also be an interface specified"
+ " as HOST:INTF. To capture inside the host namespace."
+ ),
+ "exec": "tshark -s 9200 -i {0} -P -w capture-{0}.pcap",
+ "top-level": True,
+ "new-window": {"background": True},
+ },
+ {
+ "name": "term",
+ "format": "term HOST [HOST ...]",
+ "help": "open terminal[s] (TMUX or XTerm) on HOST[S], * for all",
+ "exec": "bash",
+ "new-window": True,
+ },
+ {
+ "name": "vtysh",
+ "exec": "/usr/bin/vtysh",
+ "format": "vtysh ROUTER [ROUTER ...]",
+ "new-window": True,
+ "kinds": ["frr"],
+ },
+ {
+ "name": "xterm",
+ "format": "xterm HOST [HOST ...]",
+ "help": "open XTerm[s] on HOST[S], * for all",
+ "exec": "bash",
+ "new-window": {
+ "forcex": True,
+ },
+ },
+ {
+ "name": "logd",
+ "exec": "tail -F %RUNDIR%/{}.log",
+ "format": "logd HOST [HOST ...] DAEMON",
+ "help": (
+ "tail -f on the logfile of the given "
+ "DAEMON for the given HOST[S]"
+ ),
+ "new-window": True,
+ },
+ {
+ "name": "stdlog",
+ "exec": (
+ "[ -e %RUNDIR%/frr.log ] && tail -F %RUNDIR%/frr.log "
+ "|| tail -F /var/log/frr.log"
+ ),
+ "format": "stdlog HOST [HOST ...]",
+ "help": "tail -f on the `frr.log` for the given HOST[S]",
+ "new-window": True,
+ },
+ {
+ "name": "stdout",
+ "exec": "tail -F %RUNDIR%/{0}.err",
+ "format": "stdout HOST [HOST ...] DAEMON",
+ "help": (
+ "tail -f on the stdout of the given DAEMON for the given HOST[S]"
+ ),
+ "new-window": True,
+ },
+ {
+ "name": "stderr",
+ "exec": "tail -F %RUNDIR%/{0}.out",
+ "format": "stderr HOST [HOST ...] DAEMON",
+ "help": (
+ "tail -f on the stderr of the given DAEMON for the given HOST[S]"
+ ),
+ "new-window": True,
+ },
+ #
+ # Non-window commands.
+ #
+ {
+ "name": "",
+ "exec": "vtysh -c '{}'",
+ "format": "[ROUTER ...] COMMAND",
+ "help": "execute vtysh COMMAND on the router[s]",
+ "kinds": ["frr"],
+ },
+ {
+ "name": "sh",
+ "format": "[HOST ...] sh <SHELL-COMMAND>",
+ "help": "execute <SHELL-COMMAND> on hosts",
+ "exec": "{}",
+ },
+ {
+ "name": "shi",
+ "format": "[HOST ...] shi <INTERACTIVE-COMMAND>",
+ "help": "execute <INTERACTIVE-COMMAND> on HOST[s]",
+ "exec": "{}",
+ "interactive": True,
+ },
+ ]
+ }
+
+ cli.add_cli_config(self, cdict)
+
+ shellopt = self.cfgopt.get_option_list("--shell")
+ if "all" in shellopt or "." in shellopt:
+ self.run_in_window("bash")
+
+ # This is expected by newer munet CLI code
+ self.config_dirname = ""
+ self.config = {}
self.logger.debug("%s: Creating", self)
@@ -219,12 +315,15 @@ class Mininet(Micronet):
host.cmd_raises("ip addr add {}/{} dev {}".format(ip, plen, first_intf))
+ # can be used by munet cli
+ host.mgmt_ip = ipaddress.ip_address(ip)
+
if "defaultRoute" in params:
host.cmd_raises(
"ip route add default {}".format(params["defaultRoute"])
)
- host.config()
+ host.config_host()
self.configured_hosts.add(name)
@@ -236,6 +335,24 @@ class Mininet(Micronet):
def start(self):
"""Start the micronet topology."""
+ pcapopt = self.cfgopt.get_option_list("--pcap")
+ if "all" in pcapopt:
+ pcapopt = self.switches.keys()
+ for pcap in pcapopt:
+ if ":" in pcap:
+ host, intf = pcap.split(":")
+ pcap = f"{host}-{intf}"
+ host = self.hosts[host]
+ else:
+ host = self
+ intf = pcap
+ pcapfile = f"{self.rundir}/capture-{pcap}.pcap"
+ host.run_in_window(
+ f"tshark -s 9200 -i {intf} -P -w {pcapfile}",
+ background=True,
+ title=f"cap:{pcap}",
+ )
+
self.logger.debug("%s: Starting (no-op).", self)
def stop(self):
@@ -250,4 +367,4 @@ class Mininet(Micronet):
Mininet.g_mnet_inst = None
def cli(self):
- cli(self)
+ cli.cli(self)
diff --git a/tests/topotests/lib/ospf.py b/tests/topotests/lib/ospf.py
index 23b1f2e533..dad87440bc 100644
--- a/tests/topotests/lib/ospf.py
+++ b/tests/topotests/lib/ospf.py
@@ -577,15 +577,15 @@ def verify_ospf_neighbor(
"ospf": {
"neighbors": {
"r1": {
- "state": "Full",
+ "nbrState": "Full",
"role": "DR"
},
"r2": {
- "state": "Full",
+ "nbrState": "Full",
"role": "DROther"
},
"r3": {
- "state": "Full",
+ "nbrState": "Full",
"role": "DROther"
}
}
@@ -642,13 +642,13 @@ def verify_ospf_neighbor(
neighbor_ip = neighbor_ip.lower()
nbr_rid = data_rid
try:
- nh_state = show_ospf_json[nbr_rid][0]["state"].split("/")[0]
- intf_state = show_ospf_json[nbr_rid][0]["state"].split("/")[1]
+ nh_state = show_ospf_json[nbr_rid][0]["nbrState"].split("/")[0]
+ intf_state = show_ospf_json[nbr_rid][0]["nbrState"].split("/")[1]
except KeyError:
errormsg = "[DUT: {}] OSPF peer {} missing".format(router, nbr_rid)
return errormsg
- nbr_state = nbr_data.setdefault("state", None)
+ nbr_state = nbr_data.setdefault("nbrState", None)
nbr_role = nbr_data.setdefault("role", None)
if nbr_state:
@@ -724,8 +724,9 @@ def verify_ospf_neighbor(
nh_state = None
neighbor_ip = neighbor_ip.lower()
nbr_rid = data_rid
+
try:
- nh_state = show_ospf_json[nbr_rid][0]["state"].split("/")[0]
+ nh_state = show_ospf_json[nbr_rid][0]["nbrState"].split("/")[0]
except KeyError:
errormsg = "[DUT: {}] OSPF peer {} missing,from " "{} ".format(
router, nbr_rid, ospf_nbr
@@ -2477,7 +2478,7 @@ def verify_ospf_gr_helper(tgen, topo, dut, input_dict=None):
input_dict = {
"helperSupport":"Disabled",
"strictLsaCheck":"Enabled",
- "restartSupoort":"Planned and Unplanned Restarts",
+ "restartSupport":"Planned and Unplanned Restarts",
"supportedGracePeriod":1800
}
result = verify_ospf_gr_helper(tgen, topo, dut, input_dict)
diff --git a/tests/topotests/lib/pim.py b/tests/topotests/lib/pim.py
index 6878d93f37..925890b324 100644
--- a/tests/topotests/lib/pim.py
+++ b/tests/topotests/lib/pim.py
@@ -5120,6 +5120,75 @@ def verify_pim6_config(tgen, input_dict, expected=True):
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
return True
+
+@retry(retry_timeout=62)
+def verify_local_mld_groups(tgen, dut, interface, group_addresses):
+ """
+ Verify local MLD groups are received from an intended interface
+ by running "show ipv6 mld join json" command
+ Parameters
+ ----------
+ * `tgen`: topogen object
+ * `dut`: device under test
+ * `interface`: interface, from which IGMP groups are configured
+ * `group_addresses`: MLD group address
+ Usage
+ -----
+ dut = "r1"
+ interface = "r1-r0-eth0"
+ group_address = "ffaa::1"
+ result = verify_local_mld_groups(tgen, dut, interface, group_address)
+ Returns
+ -------
+ errormsg(str) or True
+ """
+
+ logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name))
+
+ if dut not in tgen.routers():
+ return False
+
+ rnode = tgen.routers()[dut]
+ logger.info("[DUT: %s]: Verifying local MLD groups received:", dut)
+ show_ipv6_local_mld_json = run_frr_cmd(
+ rnode, "show ipv6 mld join json", isjson=True
+ )
+
+ if type(group_addresses) is not list:
+ group_addresses = [group_addresses]
+
+ if interface not in show_ipv6_local_mld_json["default"]:
+
+ errormsg = (
+ "[DUT %s]: Verifying local MLD group received"
+ " from interface %s [FAILED]!! " % (dut, interface)
+ )
+ return errormsg
+
+ for grp_addr in group_addresses:
+ found = False
+ if grp_addr in show_ipv6_local_mld_json["default"][interface]:
+ found = True
+ break
+ if not found:
+ errormsg = (
+ "[DUT %s]: Verifying local MLD group received"
+ " from interface %s [FAILED]!! "
+ " Expected: %s " % (dut, interface, grp_addr)
+ )
+ return errormsg
+
+ logger.info(
+ "[DUT %s]: Verifying local MLD group %s received "
+ "from interface %s [PASSED]!! ",
+ dut,
+ grp_addr,
+ interface,
+ )
+
+ logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
+ return True
+
# def cleanup(self):
# super(McastTesterHelper, self).cleanup()
diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py
index 41da660b7d..2d6138990e 100644
--- a/tests/topotests/lib/topogen.py
+++ b/tests/topotests/lib/topogen.py
@@ -25,6 +25,7 @@ Basic usage instructions:
* After running stop Mininet with: tgen.stop_topology()
"""
+import configparser
import grp
import inspect
import json
@@ -33,20 +34,16 @@ import os
import platform
import pwd
import re
+import shlex
import subprocess
import sys
from collections import OrderedDict
-if sys.version_info[0] > 2:
- import configparser
-else:
- import ConfigParser as configparser
-
import lib.topolog as topolog
from lib.micronet import Commander
from lib.micronet_compat import Mininet
from lib.topolog import logger
-from lib.topotest import g_extra_config
+from munet.testing.util import pause_test
from lib import topotest
@@ -192,7 +189,7 @@ class Topogen(object):
self._load_config()
# Create new log directory
- self.logdir = topotest.get_logs_path(g_extra_config["rundir"])
+ self.logdir = topotest.get_logs_path(topotest.g_pytest_config.option.rundir)
subprocess.check_call(
"mkdir -p {0} && chmod 1777 {0}".format(self.logdir), shell=True
)
@@ -212,7 +209,10 @@ class Topogen(object):
# Mininet(Micronet) to build the actual topology.
assert not inspect.isclass(topodef)
- self.net = Mininet(controller=None)
+ self.net = Mininet(rundir=self.logdir, pytestconfig=topotest.g_pytest_config)
+
+ # Adjust the parent namespace
+ topotest.fix_netns_limits(self.net)
# New direct way: Either a dictionary defines the topology or a build function
# is supplied, or a json filename all of which build the topology by calling
@@ -236,7 +236,6 @@ class Topogen(object):
self.add_topology_from_dict(topodef)
def add_topology_from_dict(self, topodef):
-
keylist = (
topodef.keys()
if isinstance(topodef, OrderedDict)
@@ -451,7 +450,18 @@ class Topogen(object):
first is a simple kill with no sleep, the second will sleep if not
killed and try with a different signal.
"""
+ pause = bool(self.net.cfgopt.get_option("--pause-at-end"))
+ pause = pause or bool(self.net.cfgopt.get_option("--pause"))
+ if pause:
+ try:
+ pause_test("Before MUNET delete")
+ except KeyboardInterrupt:
+ print("^C...continuing")
+ except Exception as error:
+ self.logger.error("\n...continuing after error: %s", error)
+
logger.info("stopping topology: {}".format(self.modname))
+
errors = ""
for gear in self.gears.values():
errors += gear.stop()
@@ -504,7 +514,7 @@ class Topogen(object):
def set_error(self, message, code=None):
"Sets an error message and signal other tests to skip."
- logger.info(message)
+ logger.info("setting error msg: %s", message)
# If no code is defined use a sequential number
if code is None:
@@ -749,8 +759,8 @@ class TopoRouter(TopoGear):
"""
super(TopoRouter, self).__init__(tgen, name, **params)
self.routertype = params.get("routertype", "frr")
- if "privateDirs" not in params:
- params["privateDirs"] = self.PRIVATE_DIRS
+ if "private_mounts" not in params:
+ params["private_mounts"] = self.PRIVATE_DIRS
# Propagate the router log directory
logfile = self._setup_tmpdir()
@@ -799,7 +809,7 @@ class TopoRouter(TopoGear):
grep_cmd = "grep 'ip {}' {}".format(daemonstr, source)
else:
grep_cmd = "grep 'router {}' {}".format(daemonstr, source)
- result = self.run(grep_cmd).strip()
+ result = self.run(grep_cmd, warn=False).strip()
if result:
self.load_config(daemon)
else:
@@ -822,7 +832,7 @@ class TopoRouter(TopoGear):
all routers.
"""
daemonstr = self.RD.get(daemon)
- self.logger.info('loading "{}" configuration: {}'.format(daemonstr, source))
+ self.logger.debug('loading "{}" configuration: {}'.format(daemonstr, source))
self.net.loadConf(daemonstr, source, param)
def check_router_running(self):
@@ -858,7 +868,7 @@ class TopoRouter(TopoGear):
"conf t",
"log file {}.log debug".format(daemon),
"log commands",
- "log timestamp precision 3",
+ "log timestamp precision 6",
]
),
daemon=daemon,
@@ -908,7 +918,7 @@ class TopoRouter(TopoGear):
"conf t",
"log file {}.log debug".format(daemon),
"log commands",
- "log timestamp precision 3",
+ "log timestamp precision 6",
]
),
daemon=daemon,
@@ -943,18 +953,20 @@ class TopoRouter(TopoGear):
if daemon is not None:
dparam += "-d {}".format(daemon)
- vtysh_command = 'vtysh {} -c "{}" 2>/dev/null'.format(dparam, command)
+ vtysh_command = "vtysh {} -c {} 2>/dev/null".format(
+ dparam, shlex.quote(command)
+ )
- self.logger.info('vtysh command => "{}"'.format(command))
+ self.logger.debug("vtysh command => {}".format(shlex.quote(command)))
output = self.run(vtysh_command)
dbgout = output.strip()
if dbgout:
if "\n" in dbgout:
dbgout = dbgout.replace("\n", "\n\t")
- self.logger.info("vtysh result:\n\t{}".format(dbgout))
+ self.logger.debug("vtysh result:\n\t{}".format(dbgout))
else:
- self.logger.info('vtysh result: "{}"'.format(dbgout))
+ self.logger.debug('vtysh result: "{}"'.format(dbgout))
if isjson is False:
return output
@@ -994,7 +1006,7 @@ class TopoRouter(TopoGear):
dbgcmds = commands if is_string(commands) else "\n".join(commands)
dbgcmds = "\t" + dbgcmds.replace("\n", "\n\t")
- self.logger.info("vtysh command => FILE:\n{}".format(dbgcmds))
+ self.logger.debug("vtysh command => FILE:\n{}".format(dbgcmds))
res = self.run(vtysh_command)
os.unlink(fname)
@@ -1003,9 +1015,9 @@ class TopoRouter(TopoGear):
if dbgres:
if "\n" in dbgres:
dbgres = dbgres.replace("\n", "\n\t")
- self.logger.info("vtysh result:\n\t{}".format(dbgres))
+ self.logger.debug("vtysh result:\n\t{}".format(dbgres))
else:
- self.logger.info('vtysh result: "{}"'.format(dbgres))
+ self.logger.debug('vtysh result: "{}"'.format(dbgres))
return res
def report_memory_leaks(self, testname):
@@ -1097,7 +1109,7 @@ class TopoHost(TopoGear):
* `ip`: the IP address (string) for the host interface
* `defaultRoute`: the default route that will be installed
(e.g. 'via 10.0.0.1')
- * `privateDirs`: directories that will be mounted on a different domain
+ * `private_mounts`: directories that will be mounted on a different domain
(e.g. '/etc/important_dir').
"""
super(TopoHost, self).__init__(tgen, name, **params)
@@ -1117,10 +1129,10 @@ class TopoHost(TopoGear):
def __str__(self):
gear = super(TopoHost, self).__str__()
- gear += ' TopoHost<ip="{}",defaultRoute="{}",privateDirs="{}">'.format(
+ gear += ' TopoHost<ip="{}",defaultRoute="{}",private_mounts="{}">'.format(
self.params["ip"],
self.params["defaultRoute"],
- str(self.params["privateDirs"]),
+ str(self.params["private_mounts"]),
)
return gear
@@ -1143,10 +1155,10 @@ class TopoExaBGP(TopoHost):
(e.g. 'via 10.0.0.1')
Note: the different between a host and a ExaBGP peer is that this class
- has a privateDirs already defined and contains functions to handle ExaBGP
- things.
+ has a private_mounts already defined and contains functions to handle
+ ExaBGP things.
"""
- params["privateDirs"] = self.PRIVATE_DIRS
+ params["private_mounts"] = self.PRIVATE_DIRS
super(TopoExaBGP, self).__init__(tgen, name, **params)
def __str__(self):
@@ -1191,6 +1203,7 @@ class TopoExaBGP(TopoHost):
# Diagnostic function
#
+
# Disable linter branch warning. It is expected to have these here.
# pylint: disable=R0912
def diagnose_env_linux(rundir):
diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py
index d35b908e12..967f09ecbd 100644
--- a/tests/topotests/lib/topotest.py
+++ b/tests/topotests/lib/topotest.py
@@ -9,13 +9,13 @@
# Network Device Education Foundation, Inc. ("NetDEF")
#
+import configparser
import difflib
import errno
import functools
import glob
import json
import os
-import pdb
import platform
import re
import resource
@@ -24,22 +24,17 @@ import subprocess
import sys
import tempfile
import time
+from collections.abc import Mapping
from copy import deepcopy
import lib.topolog as topolog
+from lib.micronet_compat import Node
from lib.topolog import logger
-
-if sys.version_info[0] > 2:
- import configparser
- from collections.abc import Mapping
-else:
- import ConfigParser as configparser
- from collections import Mapping
+from munet.base import Timeout
from lib import micronet
-from lib.micronet_compat import Node
-g_extra_config = {}
+g_pytest_config = None
def get_logs_path(rundir):
@@ -352,7 +347,7 @@ def run_and_expect(func, what, count=20, wait=3):
count, wait
)
- logger.info(
+ logger.debug(
"'{}' polling started (interval {} secs, maximum {} tries)".format(
func_name, wait, count
)
@@ -366,7 +361,7 @@ def run_and_expect(func, what, count=20, wait=3):
continue
end_time = time.time()
- logger.info(
+ logger.debug(
"'{}' succeeded after {:.2f} seconds".format(
func_name, end_time - start_time
)
@@ -409,7 +404,7 @@ def run_and_expect_type(func, etype, count=20, wait=3, avalue=None):
count, wait
)
- logger.info(
+ logger.debug(
"'{}' polling started (interval {} secs, maximum wait {} secs)".format(
func_name, wait, int(wait * count)
)
@@ -432,7 +427,7 @@ def run_and_expect_type(func, etype, count=20, wait=3, avalue=None):
continue
end_time = time.time()
- logger.info(
+ logger.debug(
"'{}' succeeded after {:.2f} seconds".format(
func_name, end_time - start_time
)
@@ -474,32 +469,6 @@ def int2dpid(dpid):
)
-def pid_exists(pid):
- "Check whether pid exists in the current process table."
-
- if pid <= 0:
- return False
- try:
- os.waitpid(pid, os.WNOHANG)
- except:
- pass
- try:
- os.kill(pid, 0)
- except OSError as err:
- if err.errno == errno.ESRCH:
- # ESRCH == No such process
- return False
- elif err.errno == errno.EPERM:
- # EPERM clearly means there's a process to deny access to
- return True
- else:
- # According to "man 2 kill" possible error values are
- # (EINVAL, EPERM, ESRCH)
- raise
- else:
- return True
-
-
def get_textdiff(text1, text2, title1="", title2="", **opts):
"Returns empty string if same or formatted diff"
@@ -1086,7 +1055,7 @@ def checkAddressSanitizerError(output, router, component, logdir=""):
# No Address Sanitizer Error in Output. Now check for AddressSanitizer daemon file
if logdir:
- filepattern = logdir + "/" + router + "/" + component + ".asan.*"
+ filepattern = logdir + "/" + router + ".asan." + component + ".*"
logger.debug(
"Log check for %s on %s, pattern %s\n" % (component, router, filepattern)
)
@@ -1130,7 +1099,7 @@ def _sysctl_atleast(commander, variable, min_value):
valstr = " ".join([str(x) for x in min_value])
else:
valstr = str(min_value)
- logger.info("Increasing sysctl %s from %s to %s", variable, cur_val, valstr)
+ logger.debug("Increasing sysctl %s from %s to %s", variable, cur_val, valstr)
commander.cmd_raises('sysctl -w {}="{}"\n'.format(variable, valstr))
@@ -1161,7 +1130,7 @@ def _sysctl_assure(commander, variable, value):
valstr = " ".join([str(x) for x in value])
else:
valstr = str(value)
- logger.info("Changing sysctl %s from %s to %s", variable, cur_val, valstr)
+ logger.debug("Changing sysctl %s from %s to %s", variable, cur_val, valstr)
commander.cmd_raises('sysctl -w {}="{}"\n'.format(variable, valstr))
@@ -1204,7 +1173,7 @@ def rlimit_atleast(rname, min_value, raises=False):
soft, hard = cval
if soft < min_value:
nval = (min_value, hard if min_value < hard else min_value)
- logger.info("Increasing rlimit %s from %s to %s", rname, cval, nval)
+ logger.debug("Increasing rlimit %s from %s to %s", rname, cval, nval)
resource.setrlimit(rname, nval)
except subprocess.CalledProcessError as error:
logger.warning(
@@ -1215,10 +1184,9 @@ def rlimit_atleast(rname, min_value, raises=False):
def fix_netns_limits(ns):
-
# Maximum read and write socket buffer sizes
- sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10 * 1024, 87380, 16 * 2 ** 20])
- sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10 * 1024, 87380, 16 * 2 ** 20])
+ sysctl_atleast(ns, "net.ipv4.tcp_rmem", [10 * 1024, 87380, 16 * 2**20])
+ sysctl_atleast(ns, "net.ipv4.tcp_wmem", [10 * 1024, 87380, 16 * 2**20])
sysctl_assure(ns, "net.ipv4.conf.all.rp_filter", 0)
sysctl_assure(ns, "net.ipv4.conf.default.rp_filter", 0)
@@ -1277,8 +1245,8 @@ def fix_host_limits():
sysctl_atleast(None, "net.core.netdev_max_backlog", 4 * 1024)
# Maximum read and write socket buffer sizes
- sysctl_atleast(None, "net.core.rmem_max", 16 * 2 ** 20)
- sysctl_atleast(None, "net.core.wmem_max", 16 * 2 ** 20)
+ sysctl_atleast(None, "net.core.rmem_max", 16 * 2**20)
+ sysctl_atleast(None, "net.core.wmem_max", 16 * 2**20)
# Garbage Collection Settings for ARP and Neighbors
sysctl_atleast(None, "net.ipv4.neigh.default.gc_thresh2", 4 * 1024)
@@ -1303,7 +1271,8 @@ def fix_host_limits():
def setup_node_tmpdir(logdir, name):
# Cleanup old log, valgrind, and core files.
subprocess.check_call(
- "rm -rf {0}/{1}.valgrind.* {1}.*.asan {0}/{1}/".format(logdir, name), shell=True
+ "rm -rf {0}/{1}.valgrind.* {0}/{1}.asan.* {0}/{1}/".format(logdir, name),
+ shell=True,
)
# Setup the per node directory.
@@ -1318,8 +1287,7 @@ def setup_node_tmpdir(logdir, name):
class Router(Node):
"A Node with IPv4/IPv6 forwarding enabled"
- def __init__(self, name, **params):
-
+ def __init__(self, name, *posargs, **params):
# Backward compatibility:
# Load configuration defaults like topogen.
self.config_defaults = configparser.ConfigParser(
@@ -1335,11 +1303,13 @@ class Router(Node):
os.path.join(os.path.dirname(os.path.realpath(__file__)), "../pytest.ini")
)
+ self.perf_daemons = {}
+
# If this topology is using old API and doesn't have logdir
# specified, then attempt to generate an unique logdir.
self.logdir = params.get("logdir")
if self.logdir is None:
- self.logdir = get_logs_path(g_extra_config["rundir"])
+ self.logdir = get_logs_path(g_pytest_config.getoption("--rundir"))
if not params.get("logger"):
# If logger is present topogen has already set this up
@@ -1347,7 +1317,7 @@ class Router(Node):
l = topolog.get_logger(name, log_level="debug", target=logfile)
params["logger"] = l
- super(Router, self).__init__(name, **params)
+ super(Router, self).__init__(name, *posargs, **params)
self.daemondir = None
self.hasmpls = False
@@ -1407,8 +1377,8 @@ class Router(Node):
# pylint: disable=W0221
# Some params are only meaningful for the parent class.
- def config(self, **params):
- super(Router, self).config(**params)
+ def config_host(self, **params):
+ super(Router, self).config_host(**params)
# User did not specify the daemons directory, try to autodetect it.
self.daemondir = params.get("daemondir")
@@ -1478,11 +1448,11 @@ class Router(Node):
logger.info("%s: stopping %s", self.name, ", ".join([x[0] for x in running]))
for name, pid in running:
- logger.info("{}: sending SIGTERM to {}".format(self.name, name))
+ logger.debug("{}: sending SIGTERM to {}".format(self.name, name))
try:
os.kill(pid, signal.SIGTERM)
except OSError as err:
- logger.info(
+ logger.debug(
"%s: could not kill %s (%s): %s", self.name, name, pid, str(err)
)
@@ -1526,10 +1496,13 @@ class Router(Node):
def removeIPs(self):
for interface in self.intfNames():
try:
- self.intf_ip_cmd(interface, "ip address flush " + interface)
+ self.intf_ip_cmd(interface, "ip -4 address flush " + interface)
+ self.intf_ip_cmd(
+ interface, "ip -6 address flush " + interface + " scope global"
+ )
except Exception as ex:
logger.error("%s can't remove IPs %s", self, str(ex))
- # pdb.set_trace()
+ # breakpoint()
# assert False, "can't remove IPs %s" % str(ex)
def checkCapability(self, daemon, param):
@@ -1560,7 +1533,7 @@ class Router(Node):
router_relative = os.path.join(script_dir, self.name, tail)
if self.path_exists(router_relative):
source = router_relative
- self.logger.info(
+ self.logger.debug(
"using router relative configuration: {}".format(source)
)
@@ -1595,10 +1568,7 @@ class Router(Node):
if (daemon == "zebra") and (self.daemons["mgmtd"] == 0):
# Add mgmtd with zebra - if it exists
- try:
- mgmtd_path = os.path.join(self.daemondir, "mgmtd")
- except:
- pdb.set_trace()
+ mgmtd_path = os.path.join(self.daemondir, "mgmtd")
if os.path.isfile(mgmtd_path):
self.daemons["mgmtd"] = 1
self.daemons_options["mgmtd"] = ""
@@ -1606,18 +1576,14 @@ class Router(Node):
if (daemon == "zebra") and (self.daemons["staticd"] == 0):
# Add staticd with zebra - if it exists
- try:
- staticd_path = os.path.join(self.daemondir, "staticd")
- except:
- pdb.set_trace()
-
+ staticd_path = os.path.join(self.daemondir, "staticd")
if os.path.isfile(staticd_path):
self.daemons["staticd"] = 1
self.daemons_options["staticd"] = ""
# Auto-Started staticd has no config, so it will read from zebra config
else:
- logger.info("No daemon {} known".format(daemon))
+ logger.warning("No daemon {} known".format(daemon))
# print "Daemons after:", self.daemons
def runInWindow(self, cmd, title=None):
@@ -1685,8 +1651,7 @@ class Router(Node):
# used
self.cmd("echo 100000 > /proc/sys/net/mpls/platform_labels")
- shell_routers = g_extra_config["shell"]
- if "all" in shell_routers or self.name in shell_routers:
+ if g_pytest_config.name_in_option_list(self.name, "--shell"):
self.run_in_window(os.getenv("SHELL", "bash"), title="sh-%s" % self.name)
if self.daemons["eigrpd"] == 1:
@@ -1703,8 +1668,7 @@ class Router(Node):
status = self.startRouterDaemons(tgen=tgen)
- vtysh_routers = g_extra_config["vtysh"]
- if "all" in vtysh_routers or self.name in vtysh_routers:
+ if g_pytest_config.name_in_option_list(self.name, "--vtysh"):
self.run_in_window("vtysh", title="vt-%s" % self.name)
if self.unified_config:
@@ -1724,13 +1688,13 @@ class Router(Node):
def startRouterDaemons(self, daemons=None, tgen=None):
"Starts FRR daemons for this router."
- asan_abort = g_extra_config["asan_abort"]
- gdb_breakpoints = g_extra_config["gdb_breakpoints"]
- gdb_daemons = g_extra_config["gdb_daemons"]
- gdb_routers = g_extra_config["gdb_routers"]
- valgrind_extra = g_extra_config["valgrind_extra"]
- valgrind_memleaks = g_extra_config["valgrind_memleaks"]
- strace_daemons = g_extra_config["strace_daemons"]
+ asan_abort = bool(g_pytest_config.option.asan_abort)
+ gdb_breakpoints = g_pytest_config.get_option_list("--gdb-breakpoints")
+ gdb_daemons = g_pytest_config.get_option_list("--gdb-daemons")
+ gdb_routers = g_pytest_config.get_option_list("--gdb-routers")
+ valgrind_extra = bool(g_pytest_config.option.valgrind_extra)
+ valgrind_memleaks = bool(g_pytest_config.option.valgrind_memleaks)
+ strace_daemons = g_pytest_config.get_option_list("--strace-daemons")
# Get global bundle data
if not self.path_exists("/etc/frr/support_bundle_commands.conf"):
@@ -1754,11 +1718,31 @@ class Router(Node):
self.reportCores = True
# XXX: glue code forward ported from removed function.
- if self.version == None:
+ if self.version is None:
self.version = self.cmd(
os.path.join(self.daemondir, "bgpd") + " -v"
).split()[2]
logger.info("{}: running version: {}".format(self.name, self.version))
+
+ perfds = {}
+ perf_options = g_pytest_config.get_option("--perf-options", "-g")
+ for perf in g_pytest_config.get_option("--perf", []):
+ if "," in perf:
+ daemon, routers = perf.split(",", 1)
+ perfds[daemon] = routers.split(",")
+ else:
+ daemon = perf
+ perfds[daemon] = ["all"]
+
+ logd_options = {}
+ for logd in g_pytest_config.get_option("--logd", []):
+ if "," in logd:
+ daemon, routers = logd.split(",", 1)
+ logd_options[daemon] = routers.split(",")
+ else:
+ daemon = logd
+ logd_options[daemon] = ["all"]
+
# If `daemons` was specified then some upper API called us with
# specific daemons, otherwise just use our own configuration.
daemons_list = []
@@ -1770,22 +1754,36 @@ class Router(Node):
if self.daemons[daemon] == 1:
daemons_list.append(daemon)
+ tail_log_files = []
+ check_daemon_files = []
+
def start_daemon(daemon, extra_opts=None):
daemon_opts = self.daemons_options.get(daemon, "")
+
+ # get pid and vty filenames and remove the files
+ m = re.match(r"(.* |^)-n (\d+)( ?.*|$)", daemon_opts)
+ dfname = daemon if not m else "{}-{}".format(daemon, m.group(2))
+ runbase = "/var/run/{}/{}".format(self.routertype, dfname)
+ # If this is a new system bring-up remove the pid/vty files, otherwise
+ # do not since apparently presence of the pidfile impacts BGP GR
+ self.cmd_status("rm -f {0}.pid {0}.vty".format(runbase))
+
rediropt = " > {0}.out 2> {0}.err".format(daemon)
if daemon == "snmpd":
binary = "/usr/sbin/snmpd"
cmdenv = ""
cmdopt = "{} -C -c /etc/frr/snmpd.conf -p ".format(
daemon_opts
- ) + "/var/run/{}/snmpd.pid -x /etc/frr/agentx".format(self.routertype)
+ ) + "{}.pid -x /etc/frr/agentx".format(runbase)
+ # check_daemon_files.append(runbase + ".pid")
else:
binary = os.path.join(self.daemondir, daemon)
+ check_daemon_files.extend([runbase + ".pid", runbase + ".vty"])
cmdenv = "ASAN_OPTIONS="
if asan_abort:
- cmdenv = "abort_on_error=1:"
- cmdenv += "log_path={0}/{1}.{2}.asan ".format(
+ cmdenv += "abort_on_error=1:"
+ cmdenv += "log_path={0}/{1}.asan.{2} ".format(
self.logdir, self.name, daemon
)
@@ -1808,9 +1806,15 @@ class Router(Node):
daemon, self.logdir, self.name
)
- cmdopt = "{} --command-log-always --log file:{}.log --log-level debug".format(
- daemon_opts, daemon
- )
+ cmdopt = "{} --command-log-always ".format(daemon_opts)
+ cmdopt += "--log file:{}.log --log-level debug".format(daemon)
+
+ if daemon in logd_options:
+ logdopt = logd_options[daemon]
+ if "all" in logdopt or self.name in logdopt:
+ tail_log_files.append(
+ "{}/{}/{}.log".format(self.logdir, self.name, daemon)
+ )
if extra_opts:
cmdopt += " " + extra_opts
@@ -1837,6 +1841,23 @@ class Router(Node):
logger.info(
"%s: %s %s launched in gdb window", self, self.routertype, daemon
)
+ elif daemon in perfds and (self.name in perfds[daemon] or "all" in perfds[daemon]):
+ cmdopt += rediropt
+ cmd = " ".join(["perf record {} --".format(perf_options), binary, cmdopt])
+ p = self.popen(cmd)
+ self.perf_daemons[daemon] = p
+ if p.poll() and p.returncode:
+ self.logger.error(
+ '%s: Failed to launch "%s" (%s) with perf using: %s',
+ self,
+ daemon,
+ p.returncode,
+ cmd,
+ )
+ else:
+ logger.debug(
+ "%s: %s %s started with perf", self, self.routertype, daemon
+ )
else:
if daemon != "snmpd":
cmdopt += " -d "
@@ -1859,7 +1880,7 @@ class Router(Node):
else "",
)
else:
- logger.info("%s: %s %s started", self, self.routertype, daemon)
+ logger.debug("%s: %s %s started", self, self.routertype, daemon)
# Start mgmtd first
if "mgmtd" in daemons_list:
@@ -1888,15 +1909,6 @@ class Router(Node):
while "snmpd" in daemons_list:
daemons_list.remove("snmpd")
- if daemons is None:
- # Fix Link-Local Addresses on initial startup
- # Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
- _, output, _ = self.cmd_status(
- "for i in `ls /sys/class/net/` ; do mac=`cat /sys/class/net/$i/address`; echo $i: $mac; [ -z \"$mac\" ] && continue; IFS=':'; set $mac; unset IFS; ip address add dev $i scope link fe80::$(printf %02x $((0x$1 ^ 2)))$2:${3}ff:fe$4:$5$6/64; done",
- stderr=subprocess.STDOUT,
- )
- logger.debug("Set MACs:\n%s", output)
-
# Now start all the other daemons
for daemon in daemons_list:
if self.daemons[daemon] == 0:
@@ -1904,16 +1916,50 @@ class Router(Node):
start_daemon(daemon)
# Check if daemons are running.
- rundaemons = self.cmd("ls -1 /var/run/%s/*.pid" % self.routertype)
- if re.search(r"No such file or directory", rundaemons):
- return "Daemons are not running"
+ wait_time = 30 if (gdb_routers or gdb_daemons) else 10
+ timeout = Timeout(wait_time)
+ for remaining in timeout:
+ if not check_daemon_files:
+ break
+ check = check_daemon_files[0]
+ if self.path_exists(check):
+ check_daemon_files.pop(0)
+ continue
+ self.logger.debug("Waiting {}s for {} to appear".format(remaining, check))
+ time.sleep(0.5)
+
+ if check_daemon_files:
+ assert False, "Timeout({}) waiting for {} to appear on {}".format(
+ wait_time, check_daemon_files[0], self.name
+ )
# Update the permissions on the log files
self.cmd("chown frr:frr -R {}/{}".format(self.logdir, self.name))
self.cmd("chmod ug+rwX,o+r -R {}/{}".format(self.logdir, self.name))
+ if "frr" in logd_options:
+ logdopt = logd_options["frr"]
+ if "all" in logdopt or self.name in logdopt:
+ tail_log_files.append("{}/{}/frr.log".format(self.logdir, self.name))
+
+ for tailf in tail_log_files:
+ self.run_in_window("tail -f " + tailf, title=tailf, background=True)
+
return ""
+ def pid_exists(self, pid):
+ if pid <= 0:
+ return False
+ try:
+ # If we are not using PID namespaces then we will be a parent of the pid,
+ # otherwise the init process of the PID namespace will have reaped the proc.
+ os.waitpid(pid, os.WNOHANG)
+ except Exception:
+ pass
+
+ rc, o, e = self.cmd_status("kill -0 " + str(pid), warn=False)
+ return rc == 0 or "No such process" not in e
+
def killRouterDaemons(
self, daemons, wait=True, assertOnError=True, minErrorVersion="5.1"
):
@@ -1933,15 +1979,15 @@ class Router(Node):
if re.search(r"%s" % daemon, d):
daemonpidfile = d.rstrip()
daemonpid = self.cmd("cat %s" % daemonpidfile).rstrip()
- if daemonpid.isdigit() and pid_exists(int(daemonpid)):
- logger.info(
+ if daemonpid.isdigit() and self.pid_exists(int(daemonpid)):
+ logger.debug(
"{}: killing {}".format(
self.name,
os.path.basename(daemonpidfile.rsplit(".", 1)[0]),
)
)
- os.kill(int(daemonpid), signal.SIGKILL)
- if pid_exists(int(daemonpid)):
+ self.cmd_status("kill -KILL {}".format(daemonpid))
+ if self.pid_exists(int(daemonpid)):
numRunning += 1
while wait and numRunning > 0:
sleep(
@@ -1955,7 +2001,7 @@ class Router(Node):
for d in dmns[:-1]:
if re.search(r"%s" % daemon, d):
daemonpid = self.cmd("cat %s" % d.rstrip()).rstrip()
- if daemonpid.isdigit() and pid_exists(
+ if daemonpid.isdigit() and self.pid_exists(
int(daemonpid)
):
logger.info(
@@ -1966,8 +2012,10 @@ class Router(Node):
),
)
)
- os.kill(int(daemonpid), signal.SIGKILL)
- if daemonpid.isdigit() and not pid_exists(
+ self.cmd_status(
+ "kill -KILL {}".format(daemonpid)
+ )
+ if daemonpid.isdigit() and not self.pid_exists(
int(daemonpid)
):
numRunning -= 1
@@ -2198,7 +2246,7 @@ class Router(Node):
log = self.getStdErr(daemon)
if "memstats" in log:
# Found memory leak
- logger.info(
+ logger.warning(
"\nRouter {} {} StdErr Log:\n{}".format(self.name, daemon, log)
)
if not leakfound:
diff --git a/tests/topotests/mgmt_tests/test_yang_mgmt.py b/tests/topotests/mgmt_tests/test_yang_mgmt.py
index 06c18d7dfa..921b4e622c 100644
--- a/tests/topotests/mgmt_tests/test_yang_mgmt.py
+++ b/tests/topotests/mgmt_tests/test_yang_mgmt.py
@@ -217,7 +217,9 @@ def test_mgmt_commit_check(request):
]
}
}
- result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol)
+ result = verify_rib(
+ tgen, "ipv4", dut, input_dict_4, protocol=protocol, expected=False
+ )
assert (
result is not True
), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
@@ -319,7 +321,9 @@ def test_mgmt_commit_abort(request):
]
}
}
- result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol)
+ result = verify_rib(
+ tgen, "ipv4", dut, input_dict_4, protocol=protocol, expected=False
+ )
assert (
result is not True
), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
@@ -372,7 +376,7 @@ def test_mgmt_delete_config(request):
assert (
result is True
), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
-
+
step("Mgmt delete config")
raw_config = {
"r1": {
@@ -387,7 +391,9 @@ def test_mgmt_delete_config(request):
assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
step("Verify that the route is deleted from RIB")
- result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol)
+ result = verify_rib(
+ tgen, "ipv4", dut, input_dict_4, protocol=protocol, expected=False
+ )
assert (
result is not True
), "Testcase {} : Failed" "Error: Routes is still present in RIB".format(tc_name)
@@ -461,7 +467,9 @@ def test_mgmt_chaos_stop_start_frr(request):
dut = "r1"
protocol = "static"
input_dict_4 = {"r1": {"static_routes": [{"network": "192.1.11.200/32"}]}}
- result = verify_rib(tgen, "ipv4", dut, input_dict_4, protocol=protocol)
+ result = verify_rib(
+ tgen, "ipv4", dut, input_dict_4, protocol=protocol, expected=False
+ )
assert (
result is not True
), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
diff --git a/tests/topotests/multicast_mld_join_topo1/multicast_mld_local_join.json b/tests/topotests/multicast_mld_join_topo1/multicast_mld_local_join.json
new file mode 100644
index 0000000000..d7053bf6dd
--- /dev/null
+++ b/tests/topotests/multicast_mld_join_topo1/multicast_mld_local_join.json
@@ -0,0 +1,249 @@
+{
+ "address_types": ["ipv6"],
+ "ipv6base": "fd00::",
+ "ipv6mask": 64,
+ "link_ip_start": {
+ "ipv6": "fd00::",
+ "v6mask": 64
+ },
+ "lo_prefix": {
+ "ipv6": "2001:db8:f::",
+ "v6mask": 128
+ },
+ "routers": {
+ "r1": {
+ "links": {
+ "r4": {"ipv6": "auto", "pim6": "enable"},
+ "r2": {"ipv6": "auto", "pim6": "enable"},
+ "r3": {"ipv6": "auto", "pim6": "enable"},
+ "i1": {"ipv6": "auto", "pim6": "enable"},
+ "i2": {"ipv6": "auto", "pim6": "enable"}
+ },
+
+ "bgp": {
+ "local_as": "100",
+ "router_id": "192.168.1.1",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r4": {
+ "dest_link": {
+ "r1": {}
+ }
+ },
+ "r2": {
+ "dest_link": {
+ "r1": {}
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r1": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "redistribute": [
+ {"redist_type": "static"},
+ {"redist_type": "connected"}
+ ],
+ "neighbor": {
+ "r4": {
+ "dest_link": {
+ "r1": {}
+ }
+ },
+ "r2": {
+ "dest_link": {
+ "r1": {}
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r1": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r2": {
+ "links": {
+ "lo": {"ipv6": "auto", "type": "loopback", "pim6": "enable"},
+ "r1": {"ipv6": "auto", "pim6": "enable"},
+ "r4": {"ipv6": "auto", "pim6": "enable"}
+ },
+ "bgp": {
+ "local_as": "100",
+ "router_id": "192.168.1.2",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r2": {}
+ }
+ },
+ "r4": {
+ "dest_link": {
+ "r2": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "redistribute": [
+ {"redist_type": "static"},
+ {"redist_type": "connected"}
+ ],
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r2": {}
+ }
+ },
+ "r4": {
+ "dest_link": {
+ "r2": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r3": {
+ "links": {
+ "r1": {"ipv6": "auto", "pim6": "enable"},
+ "r4": {"ipv6": "auto", "pim6": "enable"}
+ },
+ "bgp": {
+ "local_as": "100",
+ "router_id": "192.168.1.3",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r3": {}
+ }
+ },
+ "r4": {
+ "dest_link": {
+ "r3": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "redistribute": [
+ {"redist_type": "static"},
+ {"redist_type": "connected"}
+ ],
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r3": {}
+ }
+ },
+ "r4": {
+ "dest_link": {
+ "r3": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "r4": {
+ "links": {
+ "r2": {"ipv6": "auto", "pim6": "enable"},
+ "r3": {"ipv6": "auto", "pim6": "enable"},
+ "i4": {"ipv6": "auto", "pim6": "enable"},
+ "r1": {"ipv6": "auto", "pim6": "enable"}
+ },
+ "bgp": {
+ "local_as": "100",
+ "router_id": "192.168.1.4",
+ "address_family": {
+ "ipv4": {
+ "unicast": {
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r4": {}
+ }
+ },
+ "r2": {
+ "dest_link": {
+ "r4": {}
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r4": {}
+ }
+ }
+ }
+ }
+ },
+ "ipv6": {
+ "unicast": {
+ "redistribute": [
+ {"redist_type": "static"},
+ {"redist_type": "connected"}
+ ],
+ "neighbor": {
+ "r1": {
+ "dest_link": {
+ "r4": {}
+ }
+ },
+ "r2": {
+ "dest_link": {
+ "r4": {}
+ }
+ },
+ "r3": {
+ "dest_link": {
+ "r4": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "i1": {
+ "links": {
+ "r1": {"ipv6": "auto"}
+ }
+ },
+ "i2": {
+ "links": {
+ "r1": {"ipv6": "auto"}
+ }
+ },
+ "i4": {
+ "links": {
+ "r4": {"ipv6": "auto"}
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/topotests/multicast_mld_join_topo1/test_multicast_mld_local_join.py b/tests/topotests/multicast_mld_join_topo1/test_multicast_mld_local_join.py
new file mode 100644
index 0000000000..2c4fb4e998
--- /dev/null
+++ b/tests/topotests/multicast_mld_join_topo1/test_multicast_mld_local_join.py
@@ -0,0 +1,915 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2023 by VMware, Inc. ("VMware")
+#
+
+"""
+Following tests are covered to test_multicast_pim_mld_local_tier_1:
+
+Test steps
+- Create topology (setup module)
+- Bring up topology
+
+Following tests are covered:
+
+1. Verify static MLD group populated when static "ip mld join <grp>" in configured
+2. Verify mroute and upstream populated with correct OIL/IIF with static imld join
+3. Verify local MLD join not allowed for non multicast group
+4. Verify static MLD group removed from DUT while removing "ip mld join" CLI
+5. Verify static MLD groups after removing and adding MLD config
+"""
+
+import os
+import sys
+import time
+import pytest
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+sys.path.append(os.path.join(CWD, "../lib/"))
+
+# Required to instantiate the topology builder class.
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib.topogen import Topogen, get_topogen
+from re import search as re_search
+from re import findall as findall
+
+from lib.common_config import (
+ start_topology,
+ write_test_header,
+ write_test_footer,
+ step,
+ kill_router_daemons,
+ start_router_daemons,
+ reset_config_on_routers,
+ do_countdown,
+ apply_raw_config,
+ socat_send_pim6_traffic,
+)
+
+from lib.pim import (
+ create_pim_config,
+ verify_mroutes,
+ verify_upstream_iif,
+ verify_mld_groups,
+ clear_pim6_mroute,
+ McastTesterHelper,
+ verify_pim_neighbors,
+ create_mld_config,
+ verify_mld_groups,
+ verify_local_mld_groups,
+ verify_pim_rp_info,
+)
+from lib.topolog import logger
+from lib.topojson import build_config_from_json
+
+r1_r2_links = []
+r1_r3_links = []
+r2_r1_links = []
+r2_r4_links = []
+r3_r1_links = []
+r3_r4_links = []
+r4_r2_links = []
+r4_r3_links = []
+
+pytestmark = [pytest.mark.pim6d, pytest.mark.staticd]
+
+TOPOLOGY = """
+ +-------------------+
+ | |
+ i1--- R1-------R2----------R4---i2
+ | |
+ +-------R3----------+
+
+
+ Description:
+ i1, i2, i3. i4, i5, i6, i7, i8 - FRR running iperf to send MLD
+ join and traffic
+ R1 - DUT (LHR)
+ R2 - RP
+ R3 - Transit
+ R4 - (FHR)
+
+"""
+# Global variables
+
+GROUP_RANGE = "ffaa::/16"
+RP_RANGE = "ff00::/8"
+GROUP_RANGE_1 = [
+ "ffaa::1/128",
+ "ffaa::2/128",
+ "ffaa::3/128",
+ "ffaa::4/128",
+ "ffaa::5/128",
+]
+MLD_JOIN_RANGE_1 = ["ffaa::1", "ffaa::2", "ffaa::3", "ffaa::4", "ffaa::5"]
+MLD_JOIN_RANGE_2 = [
+ "ff02::1:ff00:0",
+ "ff02::d",
+ "fe80::250:56ff:feb7:d8d5",
+ "2001::4",
+ "2002::5",
+]
+
+
+def setup_module(mod):
+ """
+ Sets up the pytest environment
+
+ * `mod`: module name
+ """
+
+ testsuite_run_time = time.asctime(time.localtime(time.time()))
+ logger.info("Testsuite start time: {}".format(testsuite_run_time))
+ logger.info("=" * 40)
+ logger.info("Master Topology: \n {}".format(TOPOLOGY))
+
+ logger.info("Running setup_module to create topology")
+
+ # This function initiates the topology build with Topogen...
+ json_file = "{}/multicast_mld_local_join.json".format(CWD)
+ tgen = Topogen(json_file, mod.__name__)
+ global topo
+ topo = tgen.json_topo
+ # ... and here it calls Mininet initialization functions.
+
+ # Starting topology, create tmp files which are loaded to routers
+ # to start daemons and then start routers
+ start_topology(tgen)
+
+ # Don"t run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ # Creating configuration from JSON
+ build_config_from_json(tgen, topo)
+ # Verify PIM neighbors
+ result = verify_pim_neighbors(tgen, topo)
+ assert result is True, " Verify PIM neighbor: Failed Error: {}".format(result)
+
+ logger.info("Running setup_module() done")
+
+
+def teardown_module():
+ """Teardown the pytest environment"""
+
+ logger.info("Running teardown_module to delete topology")
+
+ tgen = get_topogen()
+
+ # Stop toplogy and Remove tmp files
+ tgen.stop_topology()
+
+ logger.info(
+ "Testsuite end time: {}".format(time.asctime(time.localtime(time.time())))
+ )
+ logger.info("=" * 40)
+
+
+#####################################################
+#
+# Testcases
+#
+#####################################################
+
+
+def test_mld_local_joins_p0(request):
+ """
+ Verify static MLD group populated when static
+ "ipv6 mld join <grp>" in configured
+ """
+
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ # Don"t run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ reset_config_on_routers(tgen)
+
+ step("configure BGP on R1, R2, R3, R4 and enable redistribute static/connected")
+ step("Enable the MLD on R11 interfac of R1 and configure local mld groups")
+ intf_r1_i1 = topo["routers"]["r1"]["links"]["i1"]["interface"]
+ intf_r1_i2 = topo["routers"]["r1"]["links"]["i2"]["interface"]
+ input_dict = {
+ "r1": {
+ "mld": {
+ "interfaces": {
+ intf_r1_i1: {"mld": {"version": "1", "join": MLD_JOIN_RANGE_1}},
+ intf_r1_i2: {"mld": {"version": "1", "join": MLD_JOIN_RANGE_1}},
+ }
+ }
+ }
+ }
+
+ result = create_mld_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step("Configure static RP for (ffaa::1-5) as R2")
+
+ input_dict = {
+ "r2": {
+ "pim6": {
+ "rp": [
+ {
+ "rp_addr": topo["routers"]["r2"]["links"]["lo"]["ipv6"].split(
+ "/"
+ )[0],
+ "group_addr_range": GROUP_RANGE,
+ }
+ ]
+ }
+ }
+ }
+ result = create_pim_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
+
+ step("verify static mld join using show ipv6 mld join")
+ dut = "r1"
+ interfaces = [intf_r1_i1, intf_r1_i2]
+ for interface in interfaces:
+ result = verify_local_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("verify mld groups using show ipv6 mld groups")
+ interfaces = [intf_r1_i1, intf_r1_i2]
+ for interface in interfaces:
+ result = verify_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ write_test_footer(tc_name)
+
+
+def test_mroute_with_mld_local_joins_p0(request):
+ """
+ Verify mroute and upstream populated with correct OIL/IIF with
+ static mld join
+ """
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ # Don"t run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ reset_config_on_routers(tgen)
+
+ step("Enable the PIM on all the interfaces of R1, R2, R3, R4")
+ step("configure BGP on R1, R2, R3, R4 and enable redistribute static/connected")
+ step("Enable the MLD on R11 interfac of R1 and configure local mld groups")
+
+ intf_r1_i1 = topo["routers"]["r1"]["links"]["i1"]["interface"]
+ intf_r1_i2 = topo["routers"]["r1"]["links"]["i2"]["interface"]
+ input_dict = {
+ "r1": {
+ "mld": {
+ "interfaces": {
+ intf_r1_i1: {"mld": {"version": "1", "join": MLD_JOIN_RANGE_1}},
+ intf_r1_i2: {"mld": {"version": "1", "join": MLD_JOIN_RANGE_1}},
+ }
+ }
+ }
+ }
+
+ result = create_mld_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step("Configure static RP for (ffaa::1-5) as R2")
+
+ input_dict = {
+ "r2": {
+ "pim6": {
+ "rp": [
+ {
+ "rp_addr": topo["routers"]["r2"]["links"]["lo"]["ipv6"].split(
+ "/"
+ )[0],
+ "group_addr_range": GROUP_RANGE,
+ }
+ ]
+ }
+ }
+ }
+ result = create_pim_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
+
+ step("verify static mld join using show ipv6 mld join")
+ dut = "r1"
+ interfaces = [intf_r1_i1, intf_r1_i2]
+ for interface in interfaces:
+ result = verify_local_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("verify mld groups using show ipv6 mld groups")
+ interfaces = [intf_r1_i1, intf_r1_i2]
+ for interface in interfaces:
+ result = verify_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("verify RP-info populated in DUT")
+ dut = "r1"
+ rp_address = topo["routers"]["r2"]["links"]["lo"]["ipv6"].split("/")[0]
+ SOURCE = "Static"
+ oif = topo["routers"]["r1"]["links"]["r2"]["interface"]
+ result = verify_pim_rp_info(tgen, topo, dut, GROUP_RANGE_1, oif, rp_address, SOURCE)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step("Send traffic from R4 to all the groups ( ffaa::1 to ffaa::5)")
+ intf_ip = topo["routers"]["i4"]["links"]["r4"]["ipv6"].split("/")[0]
+ intf = topo["routers"]["i4"]["links"]["r4"]["interface"]
+ result = socat_send_pim6_traffic(tgen, "i4", "UDP6-SEND", MLD_JOIN_RANGE_1, intf)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step(
+ "'show ipv6 mroute' showing correct RPF and OIF interface for (*,G)"
+ " and (S,G) entries on all the nodes"
+ )
+ source_i6 = topo["routers"]["i4"]["links"]["r4"]["ipv6"].split("/")[0]
+
+ intf_r1_r2 = topo["routers"]["r1"]["links"]["r2"]["interface"]
+
+ input_dict_starg = [
+ {
+ "dut": "r1",
+ "src_address": "*",
+ "iif": intf_r1_r2,
+ "oil": topo["routers"]["r1"]["links"]["i1"]["interface"],
+ },
+ {
+ "dut": "r1",
+ "src_address": "*",
+ "iif": intf_r1_r2,
+ "oil": topo["routers"]["r1"]["links"]["i2"]["interface"],
+ },
+ ]
+
+ input_dict_sg = [
+ {
+ "dut": "r1",
+ "src_address": source_i6,
+ "iif": topo["routers"]["r1"]["links"]["r4"]["interface"],
+ "oil": topo["routers"]["r1"]["links"]["i1"]["interface"],
+ },
+ {
+ "dut": "r1",
+ "src_address": source_i6,
+ "iif": topo["routers"]["r1"]["links"]["r4"]["interface"],
+ "oil": topo["routers"]["r1"]["links"]["i2"]["interface"],
+ },
+ ]
+
+ step("Verify mroutes and iff upstream for local mld groups")
+ for input_dict in [input_dict_starg, input_dict_sg]:
+ for data in input_dict:
+ result = verify_mroutes(
+ tgen,
+ data["dut"],
+ data["src_address"],
+ MLD_JOIN_RANGE_1,
+ data["iif"],
+ data["oil"],
+ )
+ assert result is True, "Testcase {} : Failed Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(
+ tgen, data["dut"], data["iif"], data["src_address"], MLD_JOIN_RANGE_1
+ )
+ assert result is True, "Testcase {} : Failed Error: {}".format(
+ tc_name, result
+ )
+
+ step("Verify mroutes not created with local interface ip ")
+ input_dict_local_sg = [
+ {
+ "dut": "r1",
+ "src_address": intf_r1_i1,
+ "iif": topo["routers"]["r1"]["links"]["r4"]["interface"],
+ "oil": topo["routers"]["r1"]["links"]["i1"]["interface"],
+ },
+ {
+ "dut": "r1",
+ "src_address": intf_r1_i2,
+ "iif": topo["routers"]["r1"]["links"]["r4"]["interface"],
+ "oil": topo["routers"]["r1"]["links"]["i2"]["interface"],
+ },
+ ]
+
+ for data in input_dict_local_sg:
+ result = verify_mroutes(
+ tgen,
+ data["dut"],
+ data["src_address"],
+ MLD_JOIN_RANGE_1,
+ data["iif"],
+ data["oil"],
+ expected=False,
+ )
+ assert result is not True, (
+ "Testcase {} : Failed Error: {}"
+ "sg created with local interface ip".format(tc_name, result)
+ )
+
+ result = verify_upstream_iif(
+ tgen,
+ data["dut"],
+ data["iif"],
+ data["src_address"],
+ MLD_JOIN_RANGE_1,
+ expected=False,
+ )
+ assert result is not True, (
+ "Testcase {} : Failed Error: {}"
+ "upstream created with local interface ip".format(tc_name, result)
+ )
+
+ write_test_footer(tc_name)
+
+
+def test_remove_add_mld_local_joins_p1(request):
+ """
+ Verify static MLD group removed from DUT while
+ removing "ip mld join" CLI
+ """
+
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ # Don"t run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ reset_config_on_routers(tgen)
+
+ step("Enable the PIM on all the interfaces of R1, R2, R3, R4")
+ step("configure BGP on R1, R2, R3, R4 and enable redistribute static/connected")
+ step("Enable the MLD on R11 interfac of R1 and configure local mld groups")
+
+ intf_r1_i1 = topo["routers"]["r1"]["links"]["i1"]["interface"]
+
+ input_dict = {
+ "r1": {
+ "mld": {
+ "interfaces": {
+ intf_r1_i1: {"mld": {"version": "1", "join": MLD_JOIN_RANGE_1}}
+ }
+ }
+ }
+ }
+
+ result = create_mld_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step("Configure static RP for (ffaa::1-5) as R2")
+
+ input_dict = {
+ "r2": {
+ "pim6": {
+ "rp": [
+ {
+ "rp_addr": topo["routers"]["r2"]["links"]["lo"]["ipv6"].split(
+ "/"
+ )[0],
+ "group_addr_range": GROUP_RANGE,
+ }
+ ]
+ }
+ }
+ }
+ result = create_pim_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
+
+ step("verify static mld join using show ipv6 mld join")
+ dut = "r1"
+ interface = intf_r1_i1
+ result = verify_local_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step("verify mld groups using show ipv6 mld groups")
+
+ interface = intf_r1_i1
+ result = verify_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step("verify RP-info populated in DUT")
+ dut = "r1"
+ rp_address = topo["routers"]["r2"]["links"]["lo"]["ipv6"].split("/")[0]
+ SOURCE = "Static"
+ oif = topo["routers"]["r1"]["links"]["r2"]["interface"]
+ result = verify_pim_rp_info(tgen, topo, dut, GROUP_RANGE_1, oif, rp_address, SOURCE)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step("Send traffic from R4 to all the groups ( ffaa::1 to ffaa::5)")
+ intf_ip = topo["routers"]["i4"]["links"]["r4"]["ipv6"].split("/")[0]
+ intf = topo["routers"]["i4"]["links"]["r4"]["interface"]
+ result = socat_send_pim6_traffic(tgen, "i4", "UDP6-SEND", MLD_JOIN_RANGE_1, intf)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step(
+ "'show ipv6 mroute' showing correct RPF and OIF interface for (*,G)"
+ " and (S,G) entries on all the nodes"
+ )
+ source_i6 = topo["routers"]["i4"]["links"]["r4"]["ipv6"].split("/")[0]
+
+ intf_r1_r2 = topo["routers"]["r1"]["links"]["r2"]["interface"]
+ input_dict_starg = [
+ {
+ "dut": "r1",
+ "src_address": "*",
+ "iif": intf_r1_r2,
+ "oil": topo["routers"]["r1"]["links"]["i1"]["interface"],
+ }
+ ]
+
+ input_dict_sg = [
+ {
+ "dut": "r1",
+ "src_address": source_i6,
+ "iif": topo["routers"]["r1"]["links"]["r4"]["interface"],
+ "oil": topo["routers"]["r1"]["links"]["i1"]["interface"],
+ }
+ ]
+
+ step("Verify mroutes and iff upstream for local mld groups")
+ for input_dict in [input_dict_starg, input_dict_sg]:
+ for data in input_dict:
+ result = verify_mroutes(
+ tgen,
+ data["dut"],
+ data["src_address"],
+ MLD_JOIN_RANGE_1,
+ data["iif"],
+ data["oil"],
+ )
+ assert result is True, "Testcase {} : Failed Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(
+ tgen, data["dut"], data["iif"], data["src_address"], MLD_JOIN_RANGE_1
+ )
+ assert result is True, "Testcase {} : Failed Error: {}".format(
+ tc_name, result
+ )
+
+ step("Remove MLD join from DUT")
+ input_dict = {
+ "r1": {
+ "mld": {
+ "interfaces": {
+ intf_r1_i1: {
+ "mld": {
+ "join": MLD_JOIN_RANGE_1,
+ "delete_attr": True,
+ }
+ }
+ }
+ }
+ }
+ }
+ result = create_mld_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step("verify static mld join removed using show ipv6 mld join")
+ dut = "r1"
+ interface = intf_r1_i1
+ result = verify_local_mld_groups(
+ tgen, dut, interface, MLD_JOIN_RANGE_1, expected=False
+ )
+ assert (
+ result is not True
+ ), "Testcase {} :Failed \n Error: {}" "MLD join still present".format(
+ tc_name, result
+ )
+
+ step("verify mld groups removed using show ipv6 mld groups")
+ interface = intf_r1_i1
+ result = verify_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1, expected=False)
+ assert (
+ result is not True
+ ), "Testcase {} :Failed \n Error: {}" "MLD groups still present".format(
+ tc_name, result
+ )
+
+ step("Verify mroutes and iff upstream for local mld groups")
+ for input_dict in [input_dict_starg, input_dict_sg]:
+ for data in input_dict:
+ result = verify_mroutes(
+ tgen,
+ data["dut"],
+ data["src_address"],
+ MLD_JOIN_RANGE_1,
+ data["iif"],
+ data["oil"],
+ expected=False,
+ )
+ assert (
+ result is not True
+ ), "Testcase {} : Failed Error: {}" "mroutes still present".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(
+ tgen,
+ data["dut"],
+ data["iif"],
+ data["src_address"],
+ MLD_JOIN_RANGE_1,
+ expected=False,
+ )
+ assert (
+ result is not True
+ ), "Testcase {} : Failed Error: {}" "mroutes still present".format(
+ tc_name, result
+ )
+
+ step("Add MLD join on DUT again")
+ input_dict = {
+ "r1": {
+ "mld": {
+ "interfaces": {
+ intf_r1_i1: {
+ "mld": {
+ "join": MLD_JOIN_RANGE_1,
+ }
+ }
+ }
+ }
+ }
+ }
+ result = create_mld_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step("verify static mld join using show ipv6 mld join")
+ dut = "r1"
+ interface = intf_r1_i1
+ result = verify_local_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step("verify mld groups using show ipv6 mld groups")
+
+ interface = intf_r1_i1
+ result = verify_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step("Verify mroutes and iff upstream for local mld groups")
+ for input_dict in [input_dict_starg, input_dict_sg]:
+ for data in input_dict:
+ result = verify_mroutes(
+ tgen,
+ data["dut"],
+ data["src_address"],
+ MLD_JOIN_RANGE_1,
+ data["iif"],
+ data["oil"],
+ )
+ assert result is True, "Testcase {} : Failed Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(
+ tgen, data["dut"], data["iif"], data["src_address"], MLD_JOIN_RANGE_1
+ )
+ assert result is True, "Testcase {} : Failed Error: {}".format(
+ tc_name, result
+ )
+
+ write_test_footer(tc_name)
+
+
+def test_remove_add_mld_config_with_local_joins_p1(request):
+ """
+ Verify static MLD groups after removing
+ and adding MLD config
+ """
+
+ tgen = get_topogen()
+ tc_name = request.node.name
+ write_test_header(tc_name)
+
+ # Don"t run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ reset_config_on_routers(tgen)
+
+ step("Enable the PIM on all the interfaces of R1, R2, R3, R4")
+ step("configure BGP on R1, R2, R3, R4 and enable redistribute static/connected")
+ step("Enable the MLD on R11 interfac of R1 and configure local mld groups")
+
+ intf_r1_i1 = topo["routers"]["r1"]["links"]["i1"]["interface"]
+ input_dict = {
+ "r1": {
+ "mld": {
+ "interfaces": {
+ intf_r1_i1: {"mld": {"version": "1", "join": MLD_JOIN_RANGE_1}}
+ }
+ }
+ }
+ }
+
+ result = create_mld_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step("Configure static RP for (ffaa::1-5) as R2")
+
+ input_dict = {
+ "r2": {
+ "pim6": {
+ "rp": [
+ {
+ "rp_addr": topo["routers"]["r2"]["links"]["lo"]["ipv6"].split(
+ "/"
+ )[0],
+ "group_addr_range": GROUP_RANGE,
+ }
+ ]
+ }
+ }
+ }
+ result = create_pim_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
+
+ step("verify static mld join using show ipv6 mld join")
+ dut = "r1"
+ interface = intf_r1_i1
+ result = verify_local_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step("verify mld groups using show ipv6 mld groups")
+ interface = intf_r1_i1
+ result = verify_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step("Send traffic from R4 to all the groups ( ffaa::1 to ffaa::5)")
+ intf_ip = topo["routers"]["i4"]["links"]["r4"]["ipv6"].split("/")[0]
+ intf = topo["routers"]["i4"]["links"]["r4"]["interface"]
+ result = socat_send_pim6_traffic(tgen, "i4", "UDP6-SEND", MLD_JOIN_RANGE_1, intf)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step(
+ "'show ipv6 mroute' showing correct RPF and OIF interface for (*,G)"
+ " and (S,G) entries on all the nodes"
+ )
+ source_i6 = topo["routers"]["i4"]["links"]["r4"]["ipv6"].split("/")[0]
+
+ intf_r1_r2 = topo["routers"]["r1"]["links"]["r2"]["interface"]
+ input_dict_starg = [
+ {
+ "dut": "r1",
+ "src_address": "*",
+ "iif": intf_r1_r2,
+ "oil": topo["routers"]["r1"]["links"]["i1"]["interface"],
+ }
+ ]
+
+ input_dict_sg = [
+ {
+ "dut": "r1",
+ "src_address": source_i6,
+ "iif": topo["routers"]["r1"]["links"]["r4"]["interface"],
+ "oil": topo["routers"]["r1"]["links"]["i1"]["interface"],
+ }
+ ]
+
+ step("Verify mroutes and iff upstream for local mld groups")
+ for input_dict in [input_dict_starg, input_dict_sg]:
+ for data in input_dict:
+ result = verify_mroutes(
+ tgen,
+ data["dut"],
+ data["src_address"],
+ MLD_JOIN_RANGE_1,
+ data["iif"],
+ data["oil"],
+ )
+ assert result is True, "Testcase {} : Failed Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(
+ tgen, data["dut"], data["iif"], data["src_address"], MLD_JOIN_RANGE_1
+ )
+ assert result is True, "Testcase {} : Failed Error: {}".format(
+ tc_name, result
+ )
+
+ step("Remove mld and mld version 2 from DUT interface")
+ input_dict = {
+ "r1": {
+ "mld": {
+ "interfaces": {intf_r1_i1: {"mld": {"version": "1", "delete": True}}}
+ }
+ }
+ }
+
+ result = create_mld_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step("verify static mld join using show ipv6 mld join")
+ dut = "r1"
+ interface = intf_r1_i1
+ result = verify_local_mld_groups(
+ tgen, dut, interface, MLD_JOIN_RANGE_1, expected=False
+ )
+ assert result is not True, "Testcase {} :Failed \n Error: {}".format(
+ tc_name, result
+ )
+
+ step("verify mld groups using show ipv6 mld groups")
+ interface = intf_r1_i1
+ result = verify_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1, expected=False)
+ assert (
+ result is not True
+ ), "Testcase {} :Failed \n Error: {}" "MLD grsp still present".format(
+ tc_name, result
+ )
+
+ step("Verify mroutes and iff upstream for local mld groups")
+ for input_dict in [input_dict_starg, input_dict_sg]:
+ for data in input_dict:
+ result = verify_mroutes(
+ tgen,
+ data["dut"],
+ data["src_address"],
+ MLD_JOIN_RANGE_1,
+ data["iif"],
+ data["oil"],
+ expected=False,
+ )
+ assert (
+ result is not True
+ ), "Testcase {} : Failed Error: {}" "mroutes still present".format(
+ tc_name, result
+ )
+
+ step("Add mld and mld version 2 from DUT interface")
+ input_dict = {
+ "r1": {
+ "mld": {
+ "interfaces": {
+ intf_r1_i1: {"mld": {"version": "1", "join": MLD_JOIN_RANGE_1}}
+ }
+ }
+ }
+ }
+
+ result = create_mld_config(tgen, topo, input_dict)
+ assert result is True, "Testcase {}: Failed Error: {}".format(tc_name, result)
+
+ step("verify static mld join using show ipv6 mld join")
+ dut = "r1"
+ interface = intf_r1_i1
+ result = verify_local_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step("verify mld groups using show ipv6 mld groups")
+ interface = intf_r1_i1
+ result = verify_mld_groups(tgen, dut, interface, MLD_JOIN_RANGE_1)
+ assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
+
+ step("Verify mroutes and iff upstream for local mld groups")
+ for input_dict in [input_dict_starg, input_dict_sg]:
+ for data in input_dict:
+ result = verify_mroutes(
+ tgen,
+ data["dut"],
+ data["src_address"],
+ MLD_JOIN_RANGE_1,
+ data["iif"],
+ data["oil"],
+ )
+ assert result is True, "Testcase {} : Failed Error: {}".format(
+ tc_name, result
+ )
+
+ result = verify_upstream_iif(
+ tgen, data["dut"], data["iif"], data["src_address"], MLD_JOIN_RANGE_1
+ )
+ assert result is True, "Testcase {} : Failed Error: {}".format(
+ tc_name, result
+ )
+
+ write_test_footer(tc_name)
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm1.py b/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm1.py
index a76ff2dd9c..87b04b41be 100644
--- a/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm1.py
+++ b/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm1.py
@@ -62,6 +62,7 @@ from lib.common_config import (
socat_send_mld_join,
socat_send_pim6_traffic,
get_frr_ipv6_linklocal,
+ kill_socat,
)
from lib.bgp import create_router_bgp
from lib.pim import (
@@ -158,10 +159,6 @@ def setup_module(mod):
# Creating configuration from JSON
build_config_from_json(tgen, tgen.json_topo)
- # XXX Replace this using "with McastTesterHelper()... " in each test if possible.
- global app_helper
- app_helper = McastTesterHelper(tgen)
-
logger.info("Running setup_module() done")
@@ -172,7 +169,8 @@ def teardown_module():
tgen = get_topogen()
- app_helper.cleanup()
+ # Clean up socat
+ kill_socat(tgen)
# Stop toplogy and Remove tmp files
tgen.stop_topology()
diff --git a/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm2.py b/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm2.py
index ceef68fece..788a839918 100644
--- a/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm2.py
+++ b/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm2.py
@@ -53,6 +53,7 @@ from lib.common_config import (
socat_send_mld_join,
socat_send_pim6_traffic,
get_frr_ipv6_linklocal,
+ kill_socat,
)
from lib.bgp import create_router_bgp
from lib.pim import (
@@ -149,10 +150,6 @@ def setup_module(mod):
# Creating configuration from JSON
build_config_from_json(tgen, tgen.json_topo)
- # XXX Replace this using "with McastTesterHelper()... " in each test if possible.
- global app_helper
- app_helper = McastTesterHelper(tgen)
-
logger.info("Running setup_module() done")
@@ -163,7 +160,8 @@ def teardown_module():
tgen = get_topogen()
- app_helper.cleanup()
+ # Clean up socat
+ kill_socat(tgen)
# Stop toplogy and Remove tmp files
tgen.stop_topology()
diff --git a/tests/topotests/multicast_pim6_static_rp_topo1/test_multicast_pim6_static_rp1.py b/tests/topotests/multicast_pim6_static_rp_topo1/test_multicast_pim6_static_rp1.py
index 95b4004e14..977cd477c8 100755
--- a/tests/topotests/multicast_pim6_static_rp_topo1/test_multicast_pim6_static_rp1.py
+++ b/tests/topotests/multicast_pim6_static_rp_topo1/test_multicast_pim6_static_rp1.py
@@ -172,6 +172,9 @@ def teardown_module():
logger.info("Running teardown_module to delete topology")
tgen = get_topogen()
+ # Clean up socat
+ kill_socat(tgen)
+
# Stop toplogy and Remove tmp files
tgen.stop_topology()
diff --git a/tests/topotests/multicast_pim6_static_rp_topo1/test_multicast_pim6_static_rp2.py b/tests/topotests/multicast_pim6_static_rp_topo1/test_multicast_pim6_static_rp2.py
index 2fedb6e517..a61164baa2 100755
--- a/tests/topotests/multicast_pim6_static_rp_topo1/test_multicast_pim6_static_rp2.py
+++ b/tests/topotests/multicast_pim6_static_rp_topo1/test_multicast_pim6_static_rp2.py
@@ -176,6 +176,9 @@ def teardown_module():
logger.info("Running teardown_module to delete topology")
tgen = get_topogen()
+ # Clean up socat
+ kill_socat(tgen)
+
# Stop toplogy and Remove tmp files
tgen.stop_topology()
diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_group_all_detail.json b/tests/topotests/multicast_pim_sm_topo3/igmp_group_all_detail.json
new file mode 100644
index 0000000000..715aa1de72
--- /dev/null
+++ b/tests/topotests/multicast_pim_sm_topo3/igmp_group_all_detail.json
@@ -0,0 +1 @@
+{"totalGroups":5,"watermarkLimit":0,"l1-i1-eth1":{"name":"l1-i1-eth1","state":"up","address":"10.0.8.2","index":"*","flagMulticast":true,"flagBroadcast":true,"lanDelayEnabled":true,"groups":[{"group":"225.1.1.2","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.1","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.3","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.5","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.4","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]}]}}
diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_brief.json b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_brief.json
new file mode 100644
index 0000000000..3bbcce1370
--- /dev/null
+++ b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_brief.json
@@ -0,0 +1,51 @@
+{
+ "totalGroups":5,
+ "watermarkLimit":0,
+ "l1-i1-eth1":{
+ "name":"l1-i1-eth1",
+ "state":"up",
+ "address":"10.0.8.2",
+ "index":"*",
+ "flagMulticast":true,
+ "flagBroadcast":true,
+ "lanDelayEnabled":true,
+ "groups":[
+ {
+ "group":"225.1.1.1",
+ "timer":"*",
+ "sourcesCount":1,
+ "version":2,
+ "uptime":"*"
+ },
+ {
+ "group":"225.1.1.2",
+ "timer":"*",
+ "sourcesCount":1,
+ "version":2,
+ "uptime":"*"
+ },
+ {
+ "group":"225.1.1.3",
+ "timer":"*",
+ "sourcesCount":1,
+ "version":2,
+ "uptime":"*"
+ },
+ {
+ "group":"225.1.1.4",
+ "timer":"*",
+ "sourcesCount":1,
+ "version":2,
+ "uptime":"*"
+ },
+ {
+ "group":"225.1.1.5",
+ "timer":"*",
+ "sourcesCount":1,
+ "version":2,
+ "uptime":"*"
+ }
+ ]
+ }
+}
+
diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_detail.json b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_detail.json
new file mode 100644
index 0000000000..876befa1b8
--- /dev/null
+++ b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_detail.json
@@ -0,0 +1 @@
+{"totalGroups":5,"watermarkLimit":0,"l1-i1-eth1":{"name":"l1-i1-eth1","state":"up","address":"10.0.8.2","index":"*","flagMulticast":true,"flagBroadcast":true,"lanDelayEnabled":true,"groups":[{"group":"225.1.1.1","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.2","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.3","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.4","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.5","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]}]}}
diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_brief.json b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_brief.json
new file mode 100644
index 0000000000..a3fb496d25
--- /dev/null
+++ b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_brief.json
@@ -0,0 +1,22 @@
+{
+ "totalGroups":5,
+ "watermarkLimit":0,
+ "l1-i1-eth1":{
+ "name":"l1-i1-eth1",
+ "state":"up",
+ "address":"10.0.8.2",
+ "index":"*",
+ "flagMulticast":true,
+ "flagBroadcast":true,
+ "lanDelayEnabled":true,
+ "groups":[
+ {
+ "group":"225.1.1.5",
+ "timer":"*",
+ "sourcesCount":1,
+ "version":2,
+ "uptime":"*"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_detail.json b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_detail.json
new file mode 100644
index 0000000000..11ac5a01e7
--- /dev/null
+++ b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_detail.json
@@ -0,0 +1 @@
+{"totalGroups":5,"watermarkLimit":0,"l1-i1-eth1":{"name":"l1-i1-eth1","state":"up","address":"10.0.8.2","index":"*","flagMulticast":true,"flagBroadcast":true,"lanDelayEnabled":true,"groups":[{"group":"225.1.1.5","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]}]}}
diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_source_single_if_group_all.json b/tests/topotests/multicast_pim_sm_topo3/igmp_source_single_if_group_all.json
new file mode 100644
index 0000000000..10ae1afc90
--- /dev/null
+++ b/tests/topotests/multicast_pim_sm_topo3/igmp_source_single_if_group_all.json
@@ -0,0 +1,61 @@
+{
+ "l1-i1-eth1":{
+ "name":"l1-i1-eth1",
+ "225.1.1.1":{
+ "group":"225.1.1.1",
+ "sources":[
+ {
+ "source":"*",
+ "timer":"*",
+ "forwarded":true,
+ "uptime":"*"
+ }
+ ]
+ },
+ "225.1.1.2":{
+ "group":"225.1.1.2",
+ "sources":[
+ {
+ "source":"*",
+ "timer":"*",
+ "forwarded":true,
+ "uptime":"*"
+ }
+ ]
+ },
+ "225.1.1.3":{
+ "group":"225.1.1.3",
+ "sources":[
+ {
+ "source":"*",
+ "timer":"*",
+ "forwarded":true,
+ "uptime":"*"
+ }
+ ]
+ },
+ "225.1.1.4":{
+ "group":"225.1.1.4",
+ "sources":[
+ {
+ "source":"*",
+ "timer":"*",
+ "forwarded":true,
+ "uptime":"*"
+ }
+ ]
+ },
+ "225.1.1.5":{
+ "group":"225.1.1.5",
+ "sources":[
+ {
+ "source":"*",
+ "timer":"*",
+ "forwarded":true,
+ "uptime":"*"
+ }
+ ]
+ }
+ }
+}
+
diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_source_single_if_single_group.json b/tests/topotests/multicast_pim_sm_topo3/igmp_source_single_if_single_group.json
new file mode 100644
index 0000000000..7a19975bee
--- /dev/null
+++ b/tests/topotests/multicast_pim_sm_topo3/igmp_source_single_if_single_group.json
@@ -0,0 +1,16 @@
+{
+ "l1-i1-eth1":{
+ "name":"l1-i1-eth1",
+ "225.1.1.4":{
+ "group":"225.1.1.4",
+ "sources":[
+ {
+ "source":"*",
+ "timer":"*",
+ "forwarded":true,
+ "uptime":"*"
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/topotests/multicast_pim_sm_topo3/test_multicast_pim_sm_topo3.py b/tests/topotests/multicast_pim_sm_topo3/test_multicast_pim_sm_topo3.py
index 2ffd3a3ac0..2c1241c0cc 100755
--- a/tests/topotests/multicast_pim_sm_topo3/test_multicast_pim_sm_topo3.py
+++ b/tests/topotests/multicast_pim_sm_topo3/test_multicast_pim_sm_topo3.py
@@ -42,6 +42,8 @@ import time
import datetime
import pytest
from time import sleep
+import json
+import functools
pytestmark = pytest.mark.pimd
@@ -54,8 +56,8 @@ sys.path.append(os.path.join(CWD, "../lib/"))
# pylint: disable=C0413
# Import topogen and topotest helpers
-from lib.topogen import Topogen, get_topogen
-
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
from lib.common_config import (
start_topology,
write_test_header,
@@ -1510,6 +1512,108 @@ def test_verify_remove_add_igmp_config_to_receiver_interface_p0(request):
)
assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result)
+ # IGMP JSON verification
+ step("Verify IGMP group and source JSON for single interface and group")
+ router = tgen.gears["l1"]
+
+ reffile = os.path.join(CWD, "igmp_group_all_detail.json")
+ expected = json.loads(open(reffile).read())
+ test_func = functools.partial(
+ topotest.router_json_cmp,
+ router,
+ "show ip igmp vrf default groups detail json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
+ assertmsg = "IGMP group detailed output on l1 for all interfaces and all groups is not as expected. Expected: {}".format(
+ expected
+ )
+ assert res is None, assertmsg
+
+ reffile = os.path.join(CWD, "igmp_single_if_group_all_brief.json")
+ expected = json.loads(open(reffile).read())
+ test_func = functools.partial(
+ topotest.router_json_cmp,
+ router,
+ "show ip igmp vrf default groups l1-i1-eth1 json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
+ assertmsg = "IGMP group output on l1 for all groups in interface l1-i1-eth1 is not as expected. Expected: {}".format(
+ expected
+ )
+ assert res is None, assertmsg
+
+ reffile = os.path.join(CWD, "igmp_single_if_group_all_detail.json")
+ expected = json.loads(open(reffile).read())
+ test_func = functools.partial(
+ topotest.router_json_cmp,
+ router,
+ "show ip igmp vrf default groups l1-i1-eth1 detail json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
+ assertmsg = "IGMP group detailed output on l1 for all groups in interface l1-i1-eth1 is not as expected. Expected: {}".format(
+ expected
+ )
+ assert res is None, assertmsg
+
+ reffile = os.path.join(CWD, "igmp_single_if_single_group_brief.json")
+ expected = json.loads(open(reffile).read())
+ test_func = functools.partial(
+ topotest.router_json_cmp,
+ router,
+ "show ip igmp vrf default groups l1-i1-eth1 225.1.1.5 json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
+ assertmsg = "IGMP group output on l1 for interface l1-i1-eth1 and group 225.1.1.5 is not as expected. Expected: {}".format(
+ expected
+ )
+ assert res is None, assertmsg
+
+ reffile = os.path.join(CWD, "igmp_single_if_single_group_detail.json")
+ expected = json.loads(open(reffile).read())
+ test_func = functools.partial(
+ topotest.router_json_cmp,
+ router,
+ "show ip igmp vrf default groups l1-i1-eth1 225.1.1.5 detail json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
+ assertmsg = "IGMP group detailed output on l1 for interface l1-i1-eth1 and group 225.1.1.5 is not as expected. Expected: {}".format(
+ expected
+ )
+ assert res is None, assertmsg
+
+ reffile = os.path.join(CWD, "igmp_source_single_if_group_all.json")
+ expected = json.loads(open(reffile).read())
+ test_func = functools.partial(
+ topotest.router_json_cmp,
+ router,
+ "show ip igmp sources l1-i1-eth1 json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
+ assertmsg = "IGMP source output on l1 for interface l1-i1-eth1 is not as expected. Expected: {}".format(
+ expected
+ )
+ assert res is None, assertmsg
+
+ reffile = os.path.join(CWD, "igmp_source_single_if_single_group.json")
+ expected = json.loads(open(reffile).read())
+ test_func = functools.partial(
+ topotest.router_json_cmp,
+ router,
+ "show ip igmp sources l1-i1-eth1 225.1.1.4 json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=60, wait=2)
+ assertmsg = "IGMP source output on l1 for interface l1-i1-eth1 and group 225.1.1.4 is not as expected. Expected: {}".format(
+ expected
+ )
+ assert res is None, assertmsg
+
step(
"Remove igmp 'no ip igmp' and 'no ip igmp version 2' from"
" receiver interface of FRR1"
diff --git a/tests/topotests/munet/__init__.py b/tests/topotests/munet/__init__.py
new file mode 100644
index 0000000000..e1f18e51e6
--- /dev/null
+++ b/tests/topotests/munet/__init__.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# September 30 2021, Christian Hopps <chopps@labn.net>
+#
+# Copyright 2021, LabN Consulting, L.L.C.
+#
+"""A module to import various objects to root namespace."""
+from .base import BaseMunet
+from .base import Bridge
+from .base import Commander
+from .base import LinuxNamespace
+from .base import SharedNamespace
+from .base import cmd_error
+from .base import comm_error
+from .base import get_exec_path
+from .base import proc_error
+from .native import L3Bridge
+from .native import L3NamespaceNode
+from .native import Munet
+from .native import to_thread
+
+
+__all__ = [
+ "BaseMunet",
+ "Bridge",
+ "Commander",
+ "L3Bridge",
+ "L3NamespaceNode",
+ "LinuxNamespace",
+ "Munet",
+ "SharedNamespace",
+ "cmd_error",
+ "comm_error",
+ "get_exec_path",
+ "proc_error",
+ "to_thread",
+]
diff --git a/tests/topotests/munet/__main__.py b/tests/topotests/munet/__main__.py
new file mode 100644
index 0000000000..4419ab94a2
--- /dev/null
+++ b/tests/topotests/munet/__main__.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# September 2 2021, Christian Hopps <chopps@labn.net>
+#
+# Copyright 2021, LabN Consulting, L.L.C.
+#
+"""The main function for standalone operation."""
+import argparse
+import asyncio
+import logging
+import logging.config
+import os
+import subprocess
+import sys
+
+from . import cli
+from . import parser
+from .base import get_event_loop
+from .cleanup import cleanup_previous
+from .compat import PytestConfig
+
+
+logger = None
+
+
+async def forever():
+ while True:
+ await asyncio.sleep(3600)
+
+
+async def run_and_wait(args, unet):
+ tasks = []
+
+ if not args.topology_only:
+ # add the cmd.wait()s returned from unet.run()
+ tasks += await unet.run()
+
+ if sys.stdin.isatty() and not args.no_cli:
+ # Run an interactive CLI
+ task = cli.async_cli(unet)
+ else:
+ if args.no_wait:
+ logger.info("Waiting for all node cmd to complete")
+ task = asyncio.gather(*tasks, return_exceptions=True)
+ else:
+ logger.info("Waiting on signal to exit")
+ task = asyncio.create_task(forever())
+ task = asyncio.gather(task, *tasks, return_exceptions=True)
+
+ try:
+ await task
+ finally:
+ # Basically we are canceling tasks from unet.run() which are just async calls to
+ # node.cmd_p.wait() so we've stopped waiting for them to complete, but not
+ # actually canceld/killed the cmd_p process.
+ for task in tasks:
+ task.cancel()
+
+
+async def async_main(args, config):
+ status = 3
+
+ # Setup the namespaces and network addressing.
+
+ unet = await parser.async_build_topology(
+ config, rundir=args.rundir, args=args, pytestconfig=PytestConfig(args)
+ )
+ logger.info("Topology up: rundir: %s", unet.rundir)
+
+ try:
+ status = await run_and_wait(args, unet)
+ except KeyboardInterrupt:
+ logger.info("Exiting, received KeyboardInterrupt in async_main")
+ except asyncio.CancelledError as ex:
+ logger.info("task canceled error: %s cleaning up", ex)
+ except Exception as error:
+ logger.info("Exiting, unexpected exception %s", error, exc_info=True)
+ else:
+ logger.info("Exiting normally")
+
+ logger.debug("main: async deleting")
+ try:
+ await unet.async_delete()
+ except KeyboardInterrupt:
+ status = 2
+ logger.warning("Received KeyboardInterrupt while cleaning up.")
+ except Exception as error:
+ status = 2
+ logger.info("Deleting, unexpected exception %s", error, exc_info=True)
+ return status
+
+
+def main(*args):
+ ap = argparse.ArgumentParser(args)
+ cap = ap.add_argument_group(title="Config", description="config related options")
+
+ cap.add_argument("-c", "--config", help="config file (yaml, toml, json, ...)")
+ cap.add_argument(
+ "-d", "--rundir", help="runtime directory for tempfiles, logs, etc"
+ )
+ cap.add_argument(
+ "--kinds-config",
+ help="kinds config file, overrides default search (yaml, toml, json, ...)",
+ )
+ cap.add_argument(
+ "--project-root", help="directory to stop searching for kinds config at"
+ )
+ rap = ap.add_argument_group(title="Runtime", description="runtime related options")
+ rap.add_argument(
+ "-C",
+ "--cleanup",
+ action="store_true",
+ help="Remove the entire rundir (not just node subdirs) prior to running.",
+ )
+ rap.add_argument(
+ "--gdb", metavar="NODE-LIST", help="comma-sep list of hosts to run gdb on"
+ )
+ rap.add_argument(
+ "--gdb-breakpoints",
+ metavar="BREAKPOINT-LIST",
+ help="comma-sep list of breakpoints to set",
+ )
+ rap.add_argument(
+ "--host",
+ action="store_true",
+ help="no isolation for top namespace, bridges exposed to default namespace",
+ )
+ rap.add_argument(
+ "--pcap",
+ metavar="TARGET-LIST",
+ help="comma-sep list of capture targets (NETWORK or NODE:IFNAME)",
+ )
+ rap.add_argument(
+ "--shell", metavar="NODE-LIST", help="comma-sep list of nodes to open shells on"
+ )
+ rap.add_argument(
+ "--stderr",
+ metavar="NODE-LIST",
+ help="comma-sep list of nodes to open windows viewing stderr",
+ )
+ rap.add_argument(
+ "--stdout",
+ metavar="NODE-LIST",
+ help="comma-sep list of nodes to open windows viewing stdout",
+ )
+ rap.add_argument(
+ "--topology-only",
+ action="store_true",
+ help="Do not run any node commands",
+ )
+ rap.add_argument("--unshare-inline", action="store_true", help=argparse.SUPPRESS)
+ rap.add_argument(
+ "--validate-only",
+ action="store_true",
+ help="Validate the config against the schema definition",
+ )
+ rap.add_argument("-v", "--verbose", action="store_true", help="be verbose")
+ rap.add_argument(
+ "-V", "--version", action="store_true", help="print the verison number and exit"
+ )
+ eap = ap.add_argument_group(title="Uncommon", description="uncommonly used options")
+ eap.add_argument("--log-config", help="logging config file (yaml, toml, json, ...)")
+ eap.add_argument(
+ "--no-kill",
+ action="store_true",
+ help="Do not kill previous running processes",
+ )
+ eap.add_argument(
+ "--no-cli", action="store_true", help="Do not run the interactive CLI"
+ )
+ eap.add_argument("--no-wait", action="store_true", help="Exit after commands")
+
+ args = ap.parse_args()
+
+ if args.version:
+ from importlib import metadata # pylint: disable=C0415
+
+ print(metadata.version("munet"))
+ sys.exit(0)
+
+ rundir = args.rundir if args.rundir else "/tmp/munet"
+ args.rundir = rundir
+
+ if args.cleanup:
+ if os.path.exists(rundir):
+ if not os.path.exists(f"{rundir}/config.json"):
+ logging.critical(
+ 'unsafe: won\'t clean up rundir "%s" as '
+ "previous config.json not present",
+ rundir,
+ )
+ sys.exit(1)
+ else:
+ subprocess.run(["/usr/bin/rm", "-rf", rundir], check=True)
+
+ subprocess.run(f"mkdir -p {rundir} && chmod 755 {rundir}", check=True, shell=True)
+ os.environ["MUNET_RUNDIR"] = rundir
+
+ parser.setup_logging(args)
+
+ global logger # pylint: disable=W0603
+ logger = logging.getLogger("munet")
+
+ config = parser.get_config(args.config)
+ logger.info("Loaded config from %s", config["config_pathname"])
+ if not config["topology"]["nodes"]:
+ logger.critical("No nodes defined in config file")
+ return 1
+
+ if not args.no_kill:
+ cleanup_previous()
+
+ loop = None
+ status = 4
+ try:
+ parser.validate_config(config, logger, args)
+ if args.validate_only:
+ return 0
+ # Executes the cmd for each node.
+ loop = get_event_loop()
+ status = loop.run_until_complete(async_main(args, config))
+ except KeyboardInterrupt:
+ logger.info("Exiting, received KeyboardInterrupt in main")
+ except Exception as error:
+ logger.info("Exiting, unexpected exception %s", error, exc_info=True)
+ finally:
+ if loop:
+ loop.close()
+
+ return status
+
+
+if __name__ == "__main__":
+ exit_status = main()
+ sys.exit(exit_status)
diff --git a/tests/topotests/munet/base.py b/tests/topotests/munet/base.py
new file mode 100644
index 0000000000..eb4b088442
--- /dev/null
+++ b/tests/topotests/munet/base.py
@@ -0,0 +1,3068 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# July 9 2021, Christian Hopps <chopps@labn.net>
+#
+# Copyright 2021, LabN Consulting, L.L.C.
+#
+"""A module that implements core functionality for library or standalone use."""
+import asyncio
+import datetime
+import errno
+import ipaddress
+import logging
+import os
+import platform
+import re
+import readline
+import shlex
+import signal
+import subprocess
+import sys
+import tempfile
+import time as time_mod
+
+from collections import defaultdict
+from pathlib import Path
+from typing import Union
+
+from . import config as munet_config
+from . import linux
+
+
+try:
+ import pexpect
+
+ from pexpect.fdpexpect import fdspawn
+ from pexpect.popen_spawn import PopenSpawn
+
+ have_pexpect = True
+except ImportError:
+ have_pexpect = False
+
+PEXPECT_PROMPT = "PEXPECT_PROMPT>"
+PEXPECT_CONTINUATION_PROMPT = "PEXPECT_PROMPT+"
+
+root_hostname = subprocess.check_output("hostname")
+our_pid = os.getpid()
+
+
+class MunetError(Exception):
+ """A generic munet error."""
+
+
+class CalledProcessError(subprocess.CalledProcessError):
+ """Improved logging subclass of subprocess.CalledProcessError."""
+
+ def __str__(self):
+ o = self.output.strip() if self.output else ""
+ e = self.stderr.strip() if self.stderr else ""
+ s = f"returncode: {self.returncode} command: {self.cmd}"
+ o = "\n\tstdout: " + o if o else ""
+ e = "\n\tstderr: " + e if e else ""
+ return s + o + e
+
+ def __repr__(self):
+ o = self.output.strip() if self.output else ""
+ e = self.stderr.strip() if self.stderr else ""
+ return f"munet.base.CalledProcessError({self.returncode}, {self.cmd}, {o}, {e})"
+
+
+class Timeout:
+ """An object to passively monitor for timeouts."""
+
+ def __init__(self, delta):
+ self.delta = datetime.timedelta(seconds=delta)
+ self.started_on = datetime.datetime.now()
+ self.expires_on = self.started_on + self.delta
+
+ def elapsed(self):
+ elapsed = datetime.datetime.now() - self.started_on
+ return elapsed.total_seconds()
+
+ def is_expired(self):
+ return datetime.datetime.now() > self.expires_on
+
+ def remaining(self):
+ remaining = self.expires_on - datetime.datetime.now()
+ return remaining.total_seconds()
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ remaining = self.remaining()
+ if remaining <= 0:
+ raise StopIteration()
+ return remaining
+
+
+def fsafe_name(name):
+ return "".join(x if x.isalnum() else "_" for x in name)
+
+
+def indent(s):
+ return "\t" + s.replace("\n", "\n\t")
+
+
+def shell_quote(command):
+ """Return command wrapped in single quotes."""
+ if sys.version_info[0] >= 3:
+ return shlex.quote(command)
+ return "'" + command.replace("'", "'\"'\"'") + "'"
+
+
+def cmd_error(rc, o, e):
+ s = f"rc {rc}"
+ o = "\n\tstdout: " + o.strip() if o and o.strip() else ""
+ e = "\n\tstderr: " + e.strip() if e and e.strip() else ""
+ return s + o + e
+
+
+def proc_str(p):
+ if hasattr(p, "args"):
+ args = p.args if isinstance(p.args, str) else " ".join(p.args)
+ else:
+ args = ""
+ return f"proc pid: {p.pid} args: {args}"
+
+
+def proc_error(p, o, e):
+ if hasattr(p, "args"):
+ args = p.args if isinstance(p.args, str) else " ".join(p.args)
+ else:
+ args = ""
+
+ s = f"rc {p.returncode} pid {p.pid}"
+ a = "\n\targs: " + args if args else ""
+ o = "\n\tstdout: " + (o.strip() if o and o.strip() else "*empty*")
+ e = "\n\tstderr: " + (e.strip() if e and e.strip() else "*empty*")
+ return s + a + o + e
+
+
+def comm_error(p):
+ rc = p.poll()
+ assert rc is not None
+ if not hasattr(p, "saved_output"):
+ p.saved_output = p.communicate()
+ return proc_error(p, *p.saved_output)
+
+
+async def acomm_error(p):
+ rc = p.returncode
+ assert rc is not None
+ if not hasattr(p, "saved_output"):
+ p.saved_output = await p.communicate()
+ return proc_error(p, *p.saved_output)
+
+
+def get_kernel_version():
+ kvs = (
+ subprocess.check_output("uname -r", shell=True, text=True).strip().split("-", 1)
+ )
+ kv = kvs[0].split(".")
+ kv = [int(x) for x in kv]
+ return kv
+
+
+def convert_number(value) -> int:
+ """Convert a number value with a possible suffix to an integer.
+
+ >>> convert_number("100k") == 100 * 1024
+ True
+ >>> convert_number("100M") == 100 * 1000 * 1000
+ True
+ >>> convert_number("100Gi") == 100 * 1024 * 1024 * 1024
+ True
+ >>> convert_number("55") == 55
+ True
+ """
+ if value is None:
+ raise ValueError("Invalid value None for convert_number")
+ rate = str(value)
+ base = 1000
+ if rate[-1] == "i":
+ base = 1024
+ rate = rate[:-1]
+ suffix = "KMGTPEZY"
+ index = suffix.find(rate[-1])
+ if index == -1:
+ base = 1024
+ index = suffix.lower().find(rate[-1])
+ if index != -1:
+ rate = rate[:-1]
+ return int(rate) * base ** (index + 1)
+
+
+def is_file_like(fo):
+ return isinstance(fo, int) or hasattr(fo, "fileno")
+
+
+def get_tc_bits_value(user_value):
+ value = convert_number(user_value) / 1000
+ return f"{value:03f}kbit"
+
+
+def get_tc_bytes_value(user_value):
+ # Raw numbers are bytes in tc
+ return convert_number(user_value)
+
+
+def get_tmp_dir(uniq):
+ return os.path.join(tempfile.mkdtemp(), uniq)
+
+
+async def _async_get_exec_path(binary, cmdf, cache):
+ if isinstance(binary, str):
+ bins = [binary]
+ else:
+ bins = binary
+ for b in bins:
+ if b in cache:
+ return cache[b]
+
+ rc, output, _ = await cmdf("which " + b, warn=False)
+ if not rc:
+ cache[b] = os.path.abspath(output.strip())
+ return cache[b]
+ return None
+
+
+def _get_exec_path(binary, cmdf, cache):
+ if isinstance(binary, str):
+ bins = [binary]
+ else:
+ bins = binary
+ for b in bins:
+ if b in cache:
+ return cache[b]
+
+ rc, output, _ = cmdf("which " + b, warn=False)
+ if not rc:
+ cache[b] = os.path.abspath(output.strip())
+ return cache[b]
+ return None
+
+
+def get_event_loop():
+ """Configure and return our non-thread using event loop.
+
+ This function configures a new child watcher to not use threads.
+ Threads cannot be used when we inline unshare a PID namespace.
+ """
+ policy = asyncio.get_event_loop_policy()
+ loop = policy.get_event_loop()
+ owatcher = policy.get_child_watcher()
+ logging.debug(
+ "event_loop_fixture: global policy %s, current loop %s, current watcher %s",
+ policy,
+ loop,
+ owatcher,
+ )
+
+ policy.set_child_watcher(None)
+ owatcher.close()
+
+ try:
+ watcher = asyncio.PidfdChildWatcher() # pylint: disable=no-member
+ except Exception:
+ watcher = asyncio.SafeChildWatcher()
+ loop = policy.get_event_loop()
+
+ logging.debug(
+ "event_loop_fixture: attaching new watcher %s to loop and setting in policy",
+ watcher,
+ )
+ watcher.attach_loop(loop)
+ policy.set_child_watcher(watcher)
+ policy.set_event_loop(loop)
+ assert asyncio.get_event_loop_policy().get_child_watcher() is watcher
+
+ return loop
+
+
+class Commander: # pylint: disable=R0904
+ """An object that can execute commands."""
+
+ tmux_wait_gen = 0
+
+ def __init__(self, name, logger=None, unet=None, **kwargs):
+ """Create a Commander.
+
+ Args:
+ name: name of the commander object
+ logger: logger to use for logging commands a defualt is supplied if this
+ is None
+ unet: unet that owns this object, only used by Commander in run_in_window,
+ otherwise can be None.
+ """
+ # del kwargs # deal with lint warning
+ # logging.warning("Commander: name %s kwargs %s", name, kwargs)
+
+ self.name = name
+ self.unet = unet
+ self.deleting = False
+ self.last = None
+ self.exec_paths = {}
+
+ if not logger:
+ logname = f"munet.{self.__class__.__name__.lower()}.{name}"
+ self.logger = logging.getLogger(logname)
+ self.logger.setLevel(logging.DEBUG)
+ else:
+ self.logger = logger
+
+ super().__init__(**kwargs)
+
+ @property
+ def is_vm(self):
+ return False
+
+ @property
+ def is_container(self):
+ return False
+
+ def set_logger(self, logfile):
+ self.logger = logging.getLogger(__name__ + ".commander." + self.name)
+ self.logger.setLevel(logging.DEBUG)
+ if isinstance(logfile, str):
+ handler = logging.FileHandler(logfile, mode="w")
+ else:
+ handler = logging.StreamHandler(logfile)
+
+ fmtstr = "%(asctime)s.%(msecs)03d %(levelname)s: {}({}): %(message)s".format(
+ self.__class__.__name__, self.name
+ )
+ handler.setFormatter(logging.Formatter(fmt=fmtstr))
+ self.logger.addHandler(handler)
+
+ def _get_pre_cmd(self, use_str, use_pty, **kwargs):
+ """Get the pre-user-command values.
+
+ The values returned here should be what is required to cause the user's command
+ to execute in the correct context (e.g., namespace, container, sshremote).
+ """
+ del kwargs
+ del use_pty
+ return "" if use_str else []
+
+ def __str__(self):
+ return f"{self.__class__.__name__}({self.name})"
+
+ async def async_get_exec_path(self, binary):
+ """Return the full path to the binary executable.
+
+ `binary` :: binary name or list of binary names
+ """
+ return await _async_get_exec_path(
+ binary, self.async_cmd_status_nsonly, self.exec_paths
+ )
+
+ def get_exec_path(self, binary):
+ """Return the full path to the binary executable.
+
+ `binary` :: binary name or list of binary names
+ """
+ return _get_exec_path(binary, self.cmd_status_nsonly, self.exec_paths)
+
+ def get_exec_path_host(self, binary):
+ """Return the full path to the binary executable.
+
+ If the object is actually a derived class (e.g., a container) this method will
+ return the exec path for the native namespace rather than the container. The
+ path is the one which the other xxx_host methods will use.
+
+ `binary` :: binary name or list of binary names
+ """
+ return get_exec_path_host(binary)
+
+ def test(self, flags, arg):
+ """Run test binary, with flags and arg."""
+ test_path = self.get_exec_path(["test"])
+ rc, _, _ = self.cmd_status([test_path, flags, arg], warn=False)
+ return not rc
+
+ def test_nsonly(self, flags, arg):
+ """Run test binary, with flags and arg."""
+ test_path = self.get_exec_path(["test"])
+ rc, _, _ = self.cmd_status_nsonly([test_path, flags, arg], warn=False)
+ return not rc
+
+ def path_exists(self, path):
+ """Check if path exists."""
+ return self.test("-e", path)
+
+ async def cleanup_pid(self, pid, kill_pid=None):
+ """Signal a pid to exit with escalating forcefulness."""
+ if kill_pid is None:
+ kill_pid = pid
+
+ for sn in (signal.SIGHUP, signal.SIGKILL):
+ self.logger.debug(
+ "%s: %s %s (wait %s)", self, signal.Signals(sn).name, kill_pid, pid
+ )
+
+ os.kill(kill_pid, sn)
+
+ # No need to wait after this.
+ if sn == signal.SIGKILL:
+ return
+
+ # try each signal, waiting 15 seconds for exit before advancing
+ wait_sec = 30
+ self.logger.debug("%s: waiting %ss for pid to exit", self, wait_sec)
+ for _ in Timeout(wait_sec):
+ try:
+ status = os.waitpid(pid, os.WNOHANG)
+ if status == (0, 0):
+ await asyncio.sleep(0.1)
+ else:
+ self.logger.debug("pid %s exited status %s", pid, status)
+ return
+ except OSError as error:
+ if error.errno == errno.ECHILD:
+ self.logger.debug("%s: pid %s was reaped", self, pid)
+ else:
+ self.logger.warning(
+ "%s: error waiting on pid %s: %s", self, pid, error
+ )
+ return
+ self.logger.debug("%s: timeout waiting on pid %s to exit", self, pid)
+
+ def _get_sub_args(self, cmd_list, defaults, use_pty=False, ns_only=False, **kwargs):
+ """Returns pre-command, cmd, and default keyword args."""
+ assert not isinstance(cmd_list, str)
+
+ defaults["shell"] = False
+ pre_cmd_list = self._get_pre_cmd(False, use_pty, ns_only=ns_only, **kwargs)
+ cmd_list = [str(x) for x in cmd_list]
+
+ # os_env = {k: v for k, v in os.environ.items() if k.startswith("MUNET")}
+ # env = {**os_env, **(kwargs["env"] if "env" in kwargs else {})}
+ env = {**(kwargs["env"] if "env" in kwargs else os.environ)}
+ if "MUNET_NODENAME" not in env:
+ env["MUNET_NODENAME"] = self.name
+ kwargs["env"] = env
+
+ defaults.update(kwargs)
+
+ return pre_cmd_list, cmd_list, defaults
+
+ def _common_prologue(self, async_exec, method, cmd, skip_pre_cmd=False, **kwargs):
+ cmd_list = self._get_cmd_as_list(cmd)
+ if method == "_spawn":
+ defaults = {
+ "encoding": "utf-8",
+ "codec_errors": "ignore",
+ }
+ else:
+ defaults = {
+ "stdout": subprocess.PIPE,
+ "stderr": subprocess.PIPE,
+ }
+ if not async_exec:
+ defaults["encoding"] = "utf-8"
+
+ pre_cmd_list, cmd_list, defaults = self._get_sub_args(
+ cmd_list, defaults, **kwargs
+ )
+
+ use_pty = kwargs.get("use_pty", False)
+ if method == "_spawn":
+ # spawn doesn't take "shell" keyword arg
+ if "shell" in defaults:
+ del defaults["shell"]
+ # this is required to avoid receiving a STOPPED signal on expect!
+ if not use_pty:
+ defaults["preexec_fn"] = os.setsid
+ defaults["env"]["PS1"] = "$ "
+
+ self.logger.debug(
+ '%s: %s %s("%s", pre_cmd: "%s" use_pty: %s kwargs: %.120s)',
+ self,
+ "XXX" if method == "_spawn" else "",
+ method,
+ cmd_list,
+ pre_cmd_list if not skip_pre_cmd else "",
+ use_pty,
+ defaults,
+ )
+
+ actual_cmd_list = cmd_list if skip_pre_cmd else pre_cmd_list + cmd_list
+ return actual_cmd_list, defaults
+
+ async def _async_popen(self, method, cmd, **kwargs):
+ """Create a new asynchronous subprocess."""
+ acmd, kwargs = self._common_prologue(True, method, cmd, **kwargs)
+ p = await asyncio.create_subprocess_exec(*acmd, **kwargs)
+ return p, acmd
+
+ def _popen(self, method, cmd, **kwargs):
+ """Create a subprocess."""
+ acmd, kwargs = self._common_prologue(False, method, cmd, **kwargs)
+ p = subprocess.Popen(acmd, **kwargs)
+ return p, acmd
+
+ def _fdspawn(self, fo, **kwargs):
+ defaults = {}
+ defaults.update(kwargs)
+
+ if "echo" in defaults:
+ del defaults["echo"]
+
+ if "encoding" not in defaults:
+ defaults["encoding"] = "utf-8"
+ if "codec_errors" not in defaults:
+ defaults["codec_errors"] = "ignore"
+ encoding = defaults["encoding"]
+
+ self.logger.debug("%s: _fdspawn(%s, kwargs: %s)", self, fo, defaults)
+
+ p = fdspawn(fo, **defaults)
+
+ # We don't have TTY like conversions of LF to CRLF
+ p.crlf = os.linesep.encode(encoding)
+
+ # we own the socket now detach the file descriptor to keep it from closing
+ if hasattr(fo, "detach"):
+ fo.detach()
+
+ return p
+
+ def _spawn(self, cmd, skip_pre_cmd=False, use_pty=False, echo=False, **kwargs):
+ logging.debug(
+ '%s: XXX _spawn: cmd "%s" skip_pre_cmd %s use_pty %s echo %s kwargs %s',
+ self,
+ cmd,
+ skip_pre_cmd,
+ use_pty,
+ echo,
+ kwargs,
+ )
+ actual_cmd, defaults = self._common_prologue(
+ False, "_spawn", cmd, skip_pre_cmd=skip_pre_cmd, use_pty=use_pty, **kwargs
+ )
+
+ self.logger.debug(
+ '%s: XXX %s("%s", use_pty %s echo %s defaults: %s)',
+ self,
+ "PopenSpawn" if not use_pty else "pexpect.spawn",
+ actual_cmd,
+ use_pty,
+ echo,
+ defaults,
+ )
+
+ # We don't specify a timeout it defaults to 30s is that OK?
+ if not use_pty:
+ p = PopenSpawn(actual_cmd, **defaults)
+ else:
+ p = pexpect.spawn(actual_cmd[0], actual_cmd[1:], echo=echo, **defaults)
+ return p, actual_cmd
+
+ def spawn(
+ self,
+ cmd,
+ spawned_re,
+ expects=(),
+ sends=(),
+ use_pty=False,
+ logfile=None,
+ logfile_read=None,
+ logfile_send=None,
+ trace=None,
+ **kwargs,
+ ):
+ """Create a spawned send/expect process.
+
+ Args:
+ cmd: list of args to exec/popen with, or an already open socket
+ spawned_re: what to look for to know when done, `spawn` returns when seen
+ expects: a list of regex other than `spawned_re` to look for. Commonly,
+ "ogin:" or "[Pp]assword:"r.
+ sends: what to send when an element of `expects` matches. So e.g., the
+ username or password if thats what corresponding expect matched. Can
+ be the empty string to send nothing.
+ use_pty: true for pty based expect, otherwise uses popen (pipes/files)
+ trace: if true then log send/expects
+ **kwargs - kwargs passed on the _spawn.
+
+ Returns:
+ A pexpect process.
+
+ Raises:
+ pexpect.TIMEOUT, pexpect.EOF as documented in `pexpect`
+ CalledProcessError if EOF is seen and `cmd` exited then
+ raises a CalledProcessError to indicate the failure.
+ """
+ if is_file_like(cmd):
+ assert not use_pty
+ ac = "*socket*"
+ p = self._fdspawn(cmd, **kwargs)
+ else:
+ p, ac = self._spawn(cmd, use_pty=use_pty, **kwargs)
+
+ if logfile:
+ p.logfile = logfile
+ if logfile_read:
+ p.logfile_read = logfile_read
+ if logfile_send:
+ p.logfile_send = logfile_send
+
+ # for spawned shells (i.e., a direct command an not a console)
+ # this is wrong and will cause 2 prompts
+ if not use_pty:
+ # This isn't very nice looking
+ p.echo = False
+ if not is_file_like(cmd):
+ p.isalive = lambda: p.proc.poll() is None
+ if not hasattr(p, "close"):
+ p.close = p.wait
+
+ # Do a quick check to see if we got the prompt right away, otherwise we may be
+ # at a console so we send a \n to re-issue the prompt
+ index = p.expect([spawned_re, pexpect.TIMEOUT, pexpect.EOF], timeout=0.1)
+ if index == 0:
+ assert p.match is not None
+ self.logger.debug(
+ "%s: got spawned_re quick: '%s' matching '%s'",
+ self,
+ p.match.group(0),
+ spawned_re,
+ )
+ return p
+
+ # Now send a CRLF to cause the prompt (or whatever else) to re-issue
+ p.send("\n")
+ try:
+ patterns = [spawned_re, *expects]
+
+ self.logger.debug("%s: expecting: %s", self, patterns)
+
+ while index := p.expect(patterns):
+ if trace:
+ assert p.match is not None
+ self.logger.debug(
+ "%s: got expect: '%s' matching %d '%s', sending '%s'",
+ self,
+ p.match.group(0),
+ index,
+ patterns[index],
+ sends[index - 1],
+ )
+ if sends[index - 1]:
+ p.send(sends[index - 1])
+
+ self.logger.debug("%s: expecting again: %s", self, patterns)
+ self.logger.debug(
+ "%s: got spawned_re: '%s' matching '%s'",
+ self,
+ p.match.group(0),
+ spawned_re,
+ )
+ return p
+ except pexpect.TIMEOUT:
+ self.logger.error(
+ "%s: TIMEOUT looking for spawned_re '%s' expect buffer so far:\n%s",
+ self,
+ spawned_re,
+ indent(p.buffer),
+ )
+ raise
+ except pexpect.EOF as eoferr:
+ if p.isalive():
+ raise
+ rc = p.status
+ before = indent(p.before)
+ error = CalledProcessError(rc, ac, output=before)
+ self.logger.error(
+ "%s: EOF looking for spawned_re '%s' before EOF:\n%s",
+ self,
+ spawned_re,
+ before,
+ )
+ p.close()
+ raise error from eoferr
+
+ async def shell_spawn(
+ self,
+ cmd,
+ prompt,
+ expects=(),
+ sends=(),
+ use_pty=False,
+ will_echo=False,
+ is_bourne=True,
+ init_newline=False,
+ **kwargs,
+ ):
+ """Create a shell REPL (read-eval-print-loop).
+
+ Args:
+ cmd: shell and list of args to popen with, or an already open socket
+ prompt: the REPL prompt to look for, the function returns when seen
+ expects: a list of regex other than `spawned_re` to look for. Commonly,
+ "ogin:" or "[Pp]assword:"r.
+ sends: what to send when an element of `expects` matches. So e.g., the
+ username or password if thats what corresponding expect matched. Can
+ be the empty string to send nothing.
+ is_bourne: if False then do not modify shell prompt for internal
+ parser friently format, and do not expect continuation prompts.
+ init_newline: send an initial newline for non-bourne shell spawns, otherwise
+ expect the prompt simply from running the command
+ use_pty: true for pty based expect, otherwise uses popen (pipes/files)
+ will_echo: bash is buggy in that it echo's to non-tty unlike any other
+ sh/ksh, set this value to true if running back
+ **kwargs - kwargs passed on the _spawn.
+ """
+ combined_prompt = r"({}|{})".format(re.escape(PEXPECT_PROMPT), prompt)
+
+ assert not is_file_like(cmd) or not use_pty
+ p = self.spawn(
+ cmd,
+ combined_prompt,
+ expects=expects,
+ sends=sends,
+ use_pty=use_pty,
+ echo=False,
+ **kwargs,
+ )
+ assert not p.echo
+
+ if not is_bourne:
+ if init_newline:
+ p.send("\n")
+ return ShellWrapper(p, prompt, will_echo=will_echo)
+
+ ps1 = PEXPECT_PROMPT
+ ps2 = PEXPECT_CONTINUATION_PROMPT
+
+ # Avoid problems when =/usr/bin/env= prints the values
+ ps1p = ps1[:5] + "${UNSET_V}" + ps1[5:]
+ ps2p = ps2[:5] + "${UNSET_V}" + ps2[5:]
+
+ ps1 = re.escape(ps1)
+ ps2 = re.escape(ps2)
+
+ extra = "PAGER=cat; export PAGER; TERM=dumb; unset HISTFILE; set +o emacs +o vi"
+ pchg = "PS1='{0}' PS2='{1}' PROMPT_COMMAND=''\n".format(ps1p, ps2p)
+ p.send(pchg)
+ return ShellWrapper(p, ps1, ps2, extra_init_cmd=extra, will_echo=will_echo)
+
+ def popen(self, cmd, **kwargs):
+ """Creates a pipe with the given `command`.
+
+ Args:
+ cmd: `str` or `list` of command to open a pipe with.
+ **kwargs: kwargs is eventually passed on to Popen. If `command` is a string
+ then will be invoked with `bash -c`, otherwise `command` is a list and
+ will be invoked without a shell.
+
+ Returns:
+ a subprocess.Popen object.
+ """
+ return self._popen("popen", cmd, **kwargs)[0]
+
+ def popen_nsonly(self, cmd, **kwargs):
+ """Creates a pipe with the given `command`.
+
+ Args:
+ cmd: `str` or `list` of command to open a pipe with.
+ **kwargs: kwargs is eventually passed on to Popen. If `command` is a string
+ then will be invoked with `bash -c`, otherwise `command` is a list and
+ will be invoked without a shell.
+
+ Returns:
+ a subprocess.Popen object.
+ """
+ return self._popen("popen_nsonly", cmd, ns_only=True, **kwargs)[0]
+
+ async def async_popen(self, cmd, **kwargs):
+ """Creates a pipe with the given `command`.
+
+ Args:
+ cmd: `str` or `list` of command to open a pipe with.
+ **kwargs: kwargs is eventually passed on to create_subprocess_exec. If
+ `command` is a string then will be invoked with `bash -c`, otherwise
+ `command` is a list and will be invoked without a shell.
+
+ Returns:
+ a asyncio.subprocess.Process object.
+ """
+ p, _ = await self._async_popen("async_popen", cmd, **kwargs)
+ return p
+
+ async def async_popen_nsonly(self, cmd, **kwargs):
+ """Creates a pipe with the given `command`.
+
+ Args:
+ cmd: `str` or `list` of command to open a pipe with.
+ **kwargs: kwargs is eventually passed on to create_subprocess_exec. If
+ `command` is a string then will be invoked with `bash -c`, otherwise
+ `command` is a list and will be invoked without a shell.
+
+ Returns:
+ a asyncio.subprocess.Process object.
+ """
+ p, _ = await self._async_popen(
+ "async_popen_nsonly", cmd, ns_only=True, **kwargs
+ )
+ return p
+
+ async def async_cleanup_proc(self, p, pid=None):
+ """Terminate a process started with a popen call.
+
+ Args:
+ p: return value from :py:`async_popen`, :py:`popen`, et al.
+ pid: pid to signal instead of p.pid, typically a child of
+ cmd_p == nsenter.
+
+ Returns:
+ None on success, the ``p`` if multiple timeouts occur even
+ after a SIGKILL sent.
+ """
+ if not p:
+ return None
+
+ if p.returncode is not None:
+ if isinstance(p, subprocess.Popen):
+ o, e = p.communicate()
+ else:
+ o, e = await p.communicate()
+ self.logger.debug(
+ "%s: cmd_p already exited status: %s", self, proc_error(p, o, e)
+ )
+ return None
+
+ if pid is None:
+ pid = p.pid
+
+ self.logger.debug("%s: terminate process: %s (pid %s)", self, proc_str(p), pid)
+ try:
+ # This will SIGHUP and wait a while then SIGKILL and return immediately
+ await self.cleanup_pid(p.pid, pid)
+
+ # Wait another 2 seconds after the possible SIGKILL above for the
+ # parent nsenter to cleanup and exit
+ wait_secs = 2
+ if isinstance(p, subprocess.Popen):
+ o, e = p.communicate(timeout=wait_secs)
+ else:
+ o, e = await asyncio.wait_for(p.communicate(), timeout=wait_secs)
+ self.logger.debug(
+ "%s: cmd_p exited after kill, status: %s", self, proc_error(p, o, e)
+ )
+ except (asyncio.TimeoutError, subprocess.TimeoutExpired):
+ self.logger.warning("%s: SIGKILL timeout", self)
+ return p
+ except Exception as error:
+ self.logger.warning(
+ "%s: kill unexpected exception: %s", self, error, exc_info=True
+ )
+ return p
+ return None
+
+ @staticmethod
+ def _cmd_status_input(stdin):
+ pinput = None
+ if isinstance(stdin, (bytes, str)):
+ pinput = stdin
+ stdin = subprocess.PIPE
+ return pinput, stdin
+
+ def _cmd_status_finish(self, p, c, ac, o, e, raises, warn):
+ rc = p.returncode
+ self.last = (rc, ac, c, o, e)
+ if rc:
+ if warn:
+ self.logger.warning("%s: proc failed: %s", self, proc_error(p, o, e))
+ if raises:
+ # error = Exception("stderr: {}".format(stderr))
+ # This annoyingly doesnt' show stderr when printed normally
+ raise CalledProcessError(rc, ac, o, e)
+ return rc, o, e
+
+ def _cmd_status(self, cmds, raises=False, warn=True, stdin=None, **kwargs):
+ """Execute a command."""
+ pinput, stdin = Commander._cmd_status_input(stdin)
+ p, actual_cmd = self._popen("cmd_status", cmds, stdin=stdin, **kwargs)
+ o, e = p.communicate(pinput)
+ return self._cmd_status_finish(p, cmds, actual_cmd, o, e, raises, warn)
+
+ async def _async_cmd_status(
+ self, cmds, raises=False, warn=True, stdin=None, text=None, **kwargs
+ ):
+ """Execute a command."""
+ pinput, stdin = Commander._cmd_status_input(stdin)
+ p, actual_cmd = await self._async_popen(
+ "async_cmd_status", cmds, stdin=stdin, **kwargs
+ )
+
+ if text is False:
+ encoding = None
+ else:
+ encoding = kwargs.get("encoding", "utf-8")
+
+ if encoding is not None and isinstance(pinput, str):
+ pinput = pinput.encode(encoding)
+ o, e = await p.communicate(pinput)
+ if encoding is not None:
+ o = o.decode(encoding) if o is not None else o
+ e = e.decode(encoding) if e is not None else e
+ return self._cmd_status_finish(p, cmds, actual_cmd, o, e, raises, warn)
+
+ def _get_cmd_as_list(self, cmd):
+ """Given a list or string return a list form for execution.
+
+ If `cmd` is a string then the returned list uses bash and looks
+ like this: ["/bin/bash", "-c", cmd]. Some node types override
+ this function if they utilize a different shell as to return
+ a different list of values.
+
+ Args:
+ cmd: list or string representing the command to execute.
+
+ Returns:
+ list of commands to execute.
+ """
+ if not isinstance(cmd, str):
+ cmds = cmd
+ else:
+ # Make sure the code doesn't think `cd` will work.
+ assert not re.match(r"cd(\s*|\s+(\S+))$", cmd)
+ cmds = ["/bin/bash", "-c", cmd]
+ return cmds
+
+ def cmd_nostatus(self, cmd, **kwargs):
+ """Run given command returning output[s].
+
+ Args:
+ cmd: `str` or `list` of the command to execute. If a string is given
+ it is run using a shell, otherwise the list is executed directly
+ as the binary and arguments.
+ **kwargs: kwargs is eventually passed on to Popen. If `command` is a string
+ then will be invoked with `bash -c`, otherwise `command` is a list and
+ will be invoked without a shell.
+
+ Returns:
+ if "stderr" is in kwargs and not equal to subprocess.STDOUT, then
+ both stdout and stderr are returned, otherwise stderr is combined
+ with stdout and only stdout is returned.
+ """
+ #
+ # This method serves as the basis for all derived sync cmd variations, so to
+ # override sync cmd behavior simply override this function and *not* the other
+ # variations, unless you are changing only that variation's behavior
+ #
+
+ # XXX change this back to _cmd_status instead of cmd_status when we
+ # consolidate and cleanup the container overrides of *cmd_* functions
+
+ cmds = cmd
+ if "stderr" in kwargs and kwargs["stderr"] != subprocess.STDOUT:
+ _, o, e = self.cmd_status(cmds, **kwargs)
+ return o, e
+ if "stderr" in kwargs:
+ del kwargs["stderr"]
+ _, o, _ = self.cmd_status(cmds, stderr=subprocess.STDOUT, **kwargs)
+ return o
+
+ def cmd_status(self, cmd, **kwargs):
+ """Run given command returning status and outputs.
+
+ Args:
+ cmd: `str` or `list` of the command to execute. If a string is given
+ it is run using a shell, otherwise the list is executed directly
+ as the binary and arguments.
+ **kwargs: kwargs is eventually passed on to Popen. If `command` is a string
+ then will be invoked with `bash -c`, otherwise `command` is a list and
+ will be invoked without a shell.
+
+ Returns:
+ (status, output, error) are returned
+ status: the returncode of the command.
+ output: stdout as a string from the command.
+ error: stderr as a string from the command.
+ """
+ #
+ # This method serves as the basis for all derived sync cmd variations, so to
+ # override sync cmd behavior simply override this function and *not* the other
+ # variations, unless you are changing only that variation's behavior
+ #
+ return self._cmd_status(cmd, **kwargs)
+
+ def cmd_raises(self, cmd, **kwargs):
+ """Execute a command. Raise an exception on errors.
+
+ Args:
+ cmd: `str` or `list` of the command to execute. If a string is given
+ it is run using a shell, otherwise the list is executed directly
+ as the binary and arguments.
+ **kwargs: kwargs is eventually passed on to Popen. If `command` is a string
+ then will be invoked with `bash -c`, otherwise `command` is a list and
+ will be invoked without a shell.
+
+ Returns:
+ output: stdout as a string from the command.
+
+ Raises:
+ CalledProcessError: on non-zero exit status
+ """
+ _, stdout, _ = self._cmd_status(cmd, raises=True, **kwargs)
+ return stdout
+
+ def cmd_nostatus_nsonly(self, cmd, **kwargs):
+ # Make sure the command runs on the host and not in any container.
+ return self.cmd_nostatus(cmd, ns_only=True, **kwargs)
+
+ def cmd_status_nsonly(self, cmd, **kwargs):
+ # Make sure the command runs on the host and not in any container.
+ return self._cmd_status(cmd, ns_only=True, **kwargs)
+
+ def cmd_raises_nsonly(self, cmd, **kwargs):
+ # Make sure the command runs on the host and not in any container.
+ _, stdout, _ = self._cmd_status(cmd, raises=True, ns_only=True, **kwargs)
+ return stdout
+
+ async def async_cmd_status(self, cmd, **kwargs):
+ """Run given command returning status and outputs.
+
+ Args:
+ cmd: `str` or `list` of the command to execute. If a string is given
+ it is run using a shell, otherwise the list is executed directly
+ as the binary and arguments.
+ **kwargs: kwargs is eventually passed on to create_subprocess_exec. If
+ `cmd` is a string then will be invoked with `bash -c`, otherwise
+ `cmd` is a list and will be invoked without a shell.
+
+ Returns:
+ (status, output, error) are returned
+ status: the returncode of the command.
+ output: stdout as a string from the command.
+ error: stderr as a string from the command.
+ """
+ #
+ # This method serves as the basis for all derived async cmd variations, so to
+ # override async cmd behavior simply override this function and *not* the other
+ # variations, unless you are changing only that variation's behavior
+ #
+ return await self._async_cmd_status(cmd, **kwargs)
+
+ async def async_cmd_nostatus(self, cmd, **kwargs):
+ """Run given command returning output[s].
+
+ Args:
+ cmd: `str` or `list` of the command to execute. If a string is given
+ it is run using a shell, otherwise the list is executed directly
+ as the binary and arguments.
+ **kwargs: kwargs is eventually passed on to create_subprocess_exec. If
+ `cmd` is a string then will be invoked with `bash -c`, otherwise
+ `cmd` is a list and will be invoked without a shell.
+
+ Returns:
+ if "stderr" is in kwargs and not equal to subprocess.STDOUT, then
+ both stdout and stderr are returned, otherwise stderr is combined
+ with stdout and only stdout is returned.
+
+ """
+ # XXX change this back to _async_cmd_status instead of cmd_status when we
+ # consolidate and cleanup the container overrides of *cmd_* functions
+
+ cmds = cmd
+ if "stderr" in kwargs and kwargs["stderr"] != subprocess.STDOUT:
+ _, o, e = await self._async_cmd_status(cmds, **kwargs)
+ return o, e
+ if "stderr" in kwargs:
+ del kwargs["stderr"]
+ _, o, _ = await self._async_cmd_status(cmds, stderr=subprocess.STDOUT, **kwargs)
+ return o
+
+ async def async_cmd_raises(self, cmd, **kwargs):
+ """Execute a command. Raise an exception on errors.
+
+ Args:
+ cmd: `str` or `list` of the command to execute. If a string is given
+ it is run using a shell, otherwise the list is executed directly
+ as the binary and arguments.
+ **kwargs: kwargs is eventually passed on to create_subprocess_exec. If
+ `cmd` is a string then will be invoked with `bash -c`, otherwise
+ `cmd` is a list and will be invoked without a shell.
+
+ Returns:
+ output: stdout as a string from the command.
+
+ Raises:
+ CalledProcessError: on non-zero exit status
+ """
+ _, stdout, _ = await self._async_cmd_status(cmd, raises=True, **kwargs)
+ return stdout
+
+ async def async_cmd_status_nsonly(self, cmd, **kwargs):
+ # Make sure the command runs on the host and not in any container.
+ return await self._async_cmd_status(cmd, ns_only=True, **kwargs)
+
+ async def async_cmd_raises_nsonly(self, cmd, **kwargs):
+ # Make sure the command runs on the host and not in any container.
+ _, stdout, _ = await self._async_cmd_status(
+ cmd, raises=True, ns_only=True, **kwargs
+ )
+ return stdout
+
+ def cmd_legacy(self, cmd, **kwargs):
+ """Execute a command with stdout and stderr joined, *IGNORES ERROR*."""
+ defaults = {"stderr": subprocess.STDOUT}
+ defaults.update(kwargs)
+ _, stdout, _ = self._cmd_status(cmd, raises=False, **defaults)
+ return stdout
+
+ # Run a command in a new window (gnome-terminal, screen, tmux, xterm)
+ def run_in_window(
+ self,
+ cmd,
+ wait_for=False,
+ background=False,
+ name=None,
+ title=None,
+ forcex=False,
+ new_window=False,
+ tmux_target=None,
+ ns_only=False,
+ ):
+ """Run a command in a new window (TMUX, Screen or XTerm).
+
+ Args:
+ cmd: string to execute.
+ wait_for: True to wait for exit from command or `str` as channel neme to
+ signal on exit, otherwise False
+ background: Do not change focus to new window.
+ title: Title for new pane (tmux) or window (xterm).
+ name: Name of the new window (tmux)
+ forcex: Force use of X11.
+ new_window: Open new window (instead of pane) in TMUX
+ tmux_target: Target for tmux pane.
+
+ Returns:
+ the pane/window identifier from TMUX (depends on `new_window`)
+ """
+ channel = None
+ if isinstance(wait_for, str):
+ channel = wait_for
+ elif wait_for is True:
+ channel = "{}-wait-{}".format(our_pid, Commander.tmux_wait_gen)
+ Commander.tmux_wait_gen += 1
+
+ if forcex or ("TMUX" not in os.environ and "STY" not in os.environ):
+ root_level = False
+ else:
+ root_level = True
+
+ # SUDO: The important thing to note is that with all these methods we are
+ # executing on the users windowing system, so even though we are normally
+ # running as root, we will not be when the command is dispatched. Also
+ # in the case of SCREEN and X11 we need to sudo *back* to the user as well
+ # This is also done by SSHRemote by defualt so we should *not* sudo back
+ # if we are SSHRemote.
+
+ # XXX need to test ssh in screen
+ # XXX need to test ssh in Xterm
+ sudo_path = get_exec_path_host(["sudo"])
+ # This first test case seems same as last but using list instead of string?
+ if self.is_vm and self.use_ssh: # pylint: disable=E1101
+ if isinstance(cmd, str):
+ cmd = shlex.split(cmd)
+ cmd = ["/usr/bin/env", f"MUNET_NODENAME={self.name}"] + cmd
+
+ # get the ssh cmd
+ cmd = self._get_pre_cmd(False, True, ns_only=ns_only) + [shlex.join(cmd)]
+ unet = self.unet # pylint: disable=E1101
+ uns_cmd = unet._get_pre_cmd( # pylint: disable=W0212
+ False, True, ns_only=True, root_level=root_level
+ )
+ # get the nsenter for munet
+ nscmd = [
+ sudo_path,
+ *uns_cmd,
+ *cmd,
+ ]
+ else:
+ # This is the command to execute to be inside the namespace.
+ # We are getting into trouble with quoting.
+ # Why aren't we passing in MUNET_RUNDIR?
+ cmd = f"/usr/bin/env MUNET_NODENAME={self.name} {cmd}"
+ # We need sudo b/c we are executing as the user inside the window system.
+ sudo_path = get_exec_path_host(["sudo"])
+ nscmd = (
+ sudo_path
+ + " "
+ + self._get_pre_cmd(True, True, ns_only=ns_only, root_level=root_level)
+ + " "
+ + cmd
+ )
+
+ if "TMUX" in os.environ and not forcex:
+ cmd = [get_exec_path_host("tmux")]
+ if new_window:
+ cmd.append("new-window")
+ cmd.append("-P")
+ if name:
+ cmd.append("-n")
+ cmd.append(name)
+ if tmux_target:
+ cmd.append("-t")
+ cmd.append(tmux_target)
+ else:
+ cmd.append("split-window")
+ cmd.append("-P")
+ cmd.append("-h")
+ if not tmux_target:
+ tmux_target = os.getenv("TMUX_PANE", "")
+ if background:
+ cmd.append("-d")
+ if tmux_target:
+ cmd.append("-t")
+ cmd.append(tmux_target)
+
+ # nscmd is always added as single string argument
+ if not isinstance(nscmd, str):
+ nscmd = shlex.join(nscmd)
+ if title:
+ nscmd = f"printf '\033]2;{title}\033\\'; {nscmd}"
+ if channel:
+ nscmd = f'trap "tmux wait -S {channel}; exit 0" EXIT; {nscmd}'
+ cmd.append(nscmd)
+
+ elif "STY" in os.environ and not forcex:
+ # wait for not supported in screen for now
+ channel = None
+ cmd = [get_exec_path_host("screen")]
+ if not os.path.exists(
+ "/run/screen/S-{}/{}".format(os.environ["USER"], os.environ["STY"])
+ ):
+ # XXX not appropriate for ssh
+ cmd = ["sudo", "-Eu", os.environ["SUDO_USER"]] + cmd
+
+ if not isinstance(nscmd, str):
+ nscmd = shlex.join(nscmd)
+ cmd.append(nscmd)
+ elif "DISPLAY" in os.environ:
+ cmd = [get_exec_path_host("xterm")]
+ if "SUDO_USER" in os.environ:
+ # Do this b/c making things work as root with xauth seems hard
+ cmd = [
+ get_exec_path_host("sudo"),
+ "-Eu",
+ os.environ["SUDO_USER"],
+ ] + cmd
+ if title:
+ cmd.append("-T")
+ cmd.append(title)
+
+ cmd.append("-e")
+ if isinstance(nscmd, str):
+ cmd.extend(shlex.split(nscmd))
+ else:
+ cmd.extend(nscmd)
+
+ # if channel:
+ # return self.cmd_raises(cmd, skip_pre_cmd=True)
+ # else:
+ p = commander.popen(
+ cmd,
+ # skip_pre_cmd=True,
+ stdin=None,
+ shell=False,
+ )
+ # We should reap the child and report the error then.
+ time_mod.sleep(2)
+ if p.poll() is not None:
+ self.logger.error("%s: Failed to launch xterm: %s", self, comm_error(p))
+ return p
+ else:
+ self.logger.error(
+ "DISPLAY, STY, and TMUX not in environment, can't open window"
+ )
+ raise Exception("Window requestd but TMUX, Screen and X11 not available")
+
+ # pane_info = self.cmd_raises(cmd, skip_pre_cmd=True, ns_only=True).strip()
+ # We are prepending the nsenter command, so use unet.rootcmd
+ pane_info = commander.cmd_raises(cmd).strip()
+
+ # Re-adjust the layout
+ if "TMUX" in os.environ:
+ cmd = [
+ get_exec_path_host("tmux"),
+ "select-layout",
+ "-t",
+ pane_info if not tmux_target else tmux_target,
+ "tiled",
+ ]
+ commander.cmd_status(cmd)
+
+ # Wait here if we weren't handed the channel to wait for
+ if channel and wait_for is True:
+ cmd = [get_exec_path_host("tmux"), "wait", channel]
+ # commander.cmd_status(cmd, skip_pre_cmd=True)
+ commander.cmd_status(cmd)
+
+ return pane_info
+
+ def delete(self):
+ """Calls self.async_delete within an exec loop."""
+ asyncio.run(self.async_delete())
+
+ async def _async_delete(self):
+ """Delete this objects resources.
+
+ This is the actual implementation of the resource cleanup, each class
+ should cleanup it's own resources, generally catching and reporting,
+ but not reraising any exceptions for it's own cleanup, then it should
+ invoke `super()._async_delete() without catching any exceptions raised
+ therein. See other examples in `base.py` or `native.py`
+ """
+ self.logger.info("%s: deleted", self)
+
+ async def async_delete(self):
+ """Delete the Commander (or derived object).
+
+ The actual implementation for any class should be in `_async_delete`
+ new derived classes should look at the documentation for that function.
+ """
+ try:
+ self.deleting = True
+ await self._async_delete()
+ except Exception as error:
+ self.logger.error("%s: error while deleting: %s", self, error)
+
+
+class InterfaceMixin:
+ """A mixin class to support interface functionality."""
+
+ def __init__(self, *args, **kwargs):
+ # del kwargs # get rid of lint
+ # logging.warning("InterfaceMixin: args: %s kwargs: %s", args, kwargs)
+
+ self._intf_addrs = defaultdict(lambda: [None, None])
+ self.net_intfs = {}
+ self.next_intf_index = 0
+ self.basename = "eth"
+ # self.basename = name + "-eth"
+ super().__init__(*args, **kwargs)
+
+ @property
+ def intfs(self):
+ return sorted(self._intf_addrs.keys())
+
+ @property
+ def networks(self):
+ return sorted(self.net_intfs.keys())
+
+ def get_intf_addr(self, ifname, ipv6=False):
+ if ifname not in self._intf_addrs:
+ return None
+ return self._intf_addrs[ifname][bool(ipv6)]
+
+ def set_intf_addr(self, ifname, ifaddr):
+ ifaddr = ipaddress.ip_interface(ifaddr)
+ self._intf_addrs[ifname][ifaddr.version == 6] = ifaddr
+
+ def net_addr(self, netname, ipv6=False):
+ if netname not in self.net_intfs:
+ return None
+ return self.get_intf_addr(self.net_intfs[netname], ipv6=ipv6)
+
+ def set_intf_basename(self, basename):
+ self.basename = basename
+
+ def get_next_intf_name(self):
+ while True:
+ ifname = self.basename + str(self.next_intf_index)
+ self.next_intf_index += 1
+ if ifname not in self._intf_addrs:
+ break
+ return ifname
+
+ def get_ns_ifname(self, ifname):
+ """Return a namespace unique interface name.
+
+ This function is primarily overriden by L3QemuVM, IOW by any class
+ that doesn't create it's own network namespace and will share that
+ with the root (unet) namespace.
+
+ Args:
+ ifname: the interface name.
+
+ Returns:
+ A name unique to the namespace of this object. By defualt the assumption
+ is the ifname is namespace unique.
+ """
+ return ifname
+
+ def register_interface(self, ifname):
+ if ifname not in self._intf_addrs:
+ self._intf_addrs[ifname] = [None, None]
+
+ def register_network(self, netname, ifname):
+ if netname in self.net_intfs:
+ assert self.net_intfs[netname] == ifname
+ else:
+ self.net_intfs[netname] = ifname
+
+ def get_linux_tc_args(self, ifname, config):
+ """Get interface constraints (jitter, delay, rate) for linux TC.
+
+ The keys and their values are as follows:
+
+ delay (int): number of microseconds
+ jitter (int): number of microseconds
+ jitter-correlation (float): % correlation to previous (default 10%)
+ loss (float): % of loss
+ loss-correlation (float): % correlation to previous (default 0%)
+ rate (int or str): bits per second, string allows for use of
+ {KMGTKiMiGiTi} prefixes "i" means K == 1024 otherwise K == 1000
+ """
+ del ifname # unused
+
+ netem_args = ""
+
+ def get_number(c, v, d=None):
+ if v not in c or c[v] is None:
+ return d
+ return convert_number(c[v])
+
+ delay = get_number(config, "delay")
+ if delay is not None:
+ netem_args += f" delay {delay}usec"
+
+ jitter = get_number(config, "jitter")
+ if jitter is not None:
+ if not delay:
+ raise ValueError("jitter but no delay specified")
+ jitter_correlation = get_number(config, "jitter-correlation", 10)
+ netem_args += f" {jitter}usec {jitter_correlation}%"
+
+ loss = get_number(config, "loss")
+ if loss is not None:
+ loss_correlation = get_number(config, "loss-correlation", 0)
+ if loss_correlation:
+ netem_args += f" loss {loss}% {loss_correlation}%"
+ else:
+ netem_args += f" loss {loss}%"
+
+ if (o_rate := config.get("rate")) is None:
+ return netem_args, ""
+
+ #
+ # This comment is not correct, but is trying to talk through/learn the
+ # machinery.
+ #
+ # tokens arrive at `rate` into token buffer.
+ # limit - number of bytes that can be queued waiting for tokens
+ # -or-
+ # latency - maximum amount of time a packet may sit in TBF queue
+ #
+ # So this just allows receiving faster than rate for latency amount of
+ # time, before dropping.
+ #
+ # latency = sizeofbucket(limit) / rate (peakrate?)
+ #
+ # 32kbit
+ # -------- = latency = 320ms
+ # 100kbps
+ #
+ # -but then-
+ # burst ([token] buffer) the largest number of instantaneous
+ # tokens available (i.e, bucket size).
+
+ tbf_args = ""
+ DEFLIMIT = 1518 * 1
+ DEFBURST = 1518 * 2
+ try:
+ tc_rate = o_rate["rate"]
+ tc_rate = convert_number(tc_rate)
+ limit = convert_number(o_rate.get("limit", DEFLIMIT))
+ burst = convert_number(o_rate.get("burst", DEFBURST))
+ except (KeyError, TypeError):
+ tc_rate = convert_number(o_rate)
+ limit = convert_number(DEFLIMIT)
+ burst = convert_number(DEFBURST)
+ tbf_args += f" rate {tc_rate/1000}kbit"
+ if delay:
+ # give an extra 1/10 of buffer space to handle delay
+ tbf_args += f" limit {limit} burst {burst}"
+ else:
+ tbf_args += f" limit {limit} burst {burst}"
+
+ return netem_args, tbf_args
+
+ def set_intf_constraints(self, ifname, **constraints):
+ """Set interface outbound constraints.
+
+ Set outbound constraints (jitter, delay, rate) for an interface. All arguments
+ may also be passed as a string and will be converted to numerical format. All
+ arguments are also optional. If not specified then that existing constraint will
+ be cleared.
+
+ Args:
+ ifname: the name of the interface
+ delay (int): number of microseconds.
+ jitter (int): number of microseconds.
+ jitter-correlation (float): Percent correlation to previous (default 10%).
+ loss (float): Percent of loss.
+ loss-correlation (float): Percent correlation to previous (default 25%).
+ rate (int): bits per second, string allows for use of
+ {KMGTKiMiGiTi} prefixes "i" means K == 1024 otherwise K == 1000.
+ """
+ nsifname = self.get_ns_ifname(ifname)
+ netem_args, tbf_args = self.get_linux_tc_args(nsifname, constraints)
+ count = 1
+ selector = f"root handle {count}:"
+ if netem_args:
+ self.cmd_raises(
+ f"tc qdisc add dev {nsifname} {selector} netem {netem_args}"
+ )
+ count += 1
+ selector = f"parent {count-1}: handle {count}"
+ # Place rate limit after delay otherwise limit/burst too complex
+ if tbf_args:
+ self.cmd_raises(f"tc qdisc add dev {nsifname} {selector} tbf {tbf_args}")
+
+ self.cmd_raises(f"tc qdisc show dev {nsifname}")
+
+
+class LinuxNamespace(Commander, InterfaceMixin):
+ """A linux Namespace.
+
+ An object that creates and executes commands in a linux namespace
+ """
+
+ def __init__(
+ self,
+ name,
+ net=True,
+ mount=True,
+ uts=True,
+ cgroup=False,
+ ipc=False,
+ pid=False,
+ time=False,
+ user=False,
+ unshare_inline=False,
+ set_hostname=True,
+ private_mounts=None,
+ **kwargs,
+ ):
+ """Create a new linux namespace.
+
+ Args:
+ name: Internal name for the namespace.
+ net: Create network namespace.
+ mount: Create network namespace.
+ uts: Create UTS (hostname) namespace.
+ cgroup: Create cgroup namespace.
+ ipc: Create IPC namespace.
+ pid: Create PID namespace, also mounts new /proc.
+ time: Create time namespace.
+ user: Create user namespace, also keeps capabilities.
+ set_hostname: Set the hostname to `name`, uts must also be True.
+ private_mounts: List of strings of the form
+ "[/external/path:]/internal/path. If no external path is specified a
+ tmpfs is mounted on the internal path. Any paths specified are first
+ passed to `mkdir -p`.
+ unshare_inline: Unshare the process itself rather than using a proxy.
+ logger: Passed to superclass.
+ """
+ # logging.warning("LinuxNamespace: name %s kwargs %s", name, kwargs)
+
+ super().__init__(name, **kwargs)
+
+ unet = self.unet
+
+ self.logger.debug("%s: creating", self)
+
+ self.cwd = os.path.abspath(os.getcwd())
+
+ self.nsflags = []
+ self.ifnetns = {}
+ self.uflags = 0
+ self.p_ns_fds = None
+ self.p_ns_fnames = None
+ self.pid_ns = False
+ self.init_pid = None
+ self.unshare_inline = unshare_inline
+ self.nsenter_fork = True
+
+ #
+ # Collect the namespaces to unshare
+ #
+ if hasattr(self, "proc_path") and self.proc_path: # pylint: disable=no-member
+ pp = Path(self.proc_path) # pylint: disable=no-member
+ else:
+ pp = unet.proc_path if unet else Path("/proc")
+ pp = pp.joinpath("%P%", "ns")
+
+ flags = ""
+ uflags = 0
+ nslist = []
+ nsflags = []
+ if cgroup:
+ nselm = "cgroup"
+ nslist.append(nselm)
+ nsflags.append(f"--{nselm}={pp / nselm}")
+ flags += "C"
+ uflags |= linux.CLONE_NEWCGROUP
+ if ipc:
+ nselm = "ipc"
+ nslist.append(nselm)
+ nsflags.append(f"--{nselm}={pp / nselm}")
+ flags += "i"
+ uflags |= linux.CLONE_NEWIPC
+ if mount or pid:
+ # We need a new mount namespace for pid
+ nselm = "mnt"
+ nslist.append(nselm)
+ nsflags.append(f"--mount={pp / nselm}")
+ mount = True
+ flags += "m"
+ uflags |= linux.CLONE_NEWNS
+ if net:
+ nselm = "net"
+ nslist.append(nselm)
+ nsflags.append(f"--{nselm}={pp / nselm}")
+ # if pid:
+ # os.system(f"touch /tmp/netns-{name}")
+ # cmd.append(f"--net=/tmp/netns-{name}")
+ # else:
+ flags += "n"
+ uflags |= linux.CLONE_NEWNET
+ if pid:
+ self.pid_ns = True
+ # We look for this b/c the unshare pid will share with /sibn/init
+ nselm = "pid_for_children"
+ nslist.append(nselm)
+ nsflags.append(f"--pid={pp / nselm}")
+ flags += "p"
+ uflags |= linux.CLONE_NEWPID
+ if time:
+ nselm = "time"
+ # XXX time_for_children?
+ nslist.append(nselm)
+ nsflags.append(f"--{nselm}={pp / nselm}")
+ flags += "T"
+ uflags |= linux.CLONE_NEWTIME
+ if user:
+ nselm = "user"
+ nslist.append(nselm)
+ nsflags.append(f"--{nselm}={pp / nselm}")
+ flags += "U"
+ uflags |= linux.CLONE_NEWUSER
+ if uts:
+ nselm = "uts"
+ nslist.append(nselm)
+ nsflags.append(f"--{nselm}={pp / nselm}")
+ flags += "u"
+ uflags |= linux.CLONE_NEWUTS
+
+ assert flags, "LinuxNamespace with no namespaces requested"
+
+ # Should look path up using resources maybe...
+ mutini_path = get_our_script_path("mutini")
+ if not mutini_path:
+ mutini_path = get_our_script_path("mutini.py")
+ assert mutini_path
+ cmd = [mutini_path, f"--unshare-flags={flags}", "-v"]
+ fname = fsafe_name(self.name) + "-mutini.log"
+ fname = (unet or self).rundir.joinpath(fname)
+ stdout = open(fname, "w", encoding="utf-8")
+ stderr = subprocess.STDOUT
+
+ #
+ # Save the current namespace info to compare against later
+ #
+
+ if not unet:
+ nsdict = {x: os.readlink(f"/proc/self/ns/{x}") for x in nslist}
+ else:
+ nsdict = {
+ x: os.readlink(f"{unet.proc_path}/{unet.pid}/ns/{x}") for x in nslist
+ }
+
+ #
+ # (A) Basically we need to save the pid of the unshare call for nsenter.
+ #
+ # For `unet is not None` (node case) the level this exists at is based on wether
+ # unet is using a forking nsenter or not. So if unet.nsenter_fork == True then
+ # we need the child pid of the p.pid (child of pid returned to us), otherwise
+ # unet.nsenter_fork == False and we just use p.pid as it will be unshare after
+ # nsenter exec's it.
+ #
+ # For the `unet is None` (unet case) the unshare is at the top level or
+ # non-existent so we always save the returned p.pid. If we are unshare_inline we
+ # won't have a __pre_cmd but we can save our child_pid to kill later, otherwise
+ # we set unet.pid to None b/c there's literally nothing to do.
+ #
+ # ---------------------------------------------------------------------------
+ # Breakdown for nested (non-unet) namespace creation, and what PID
+ # to use for __pre_cmd nsenter use.
+ # ---------------------------------------------------------------------------
+ #
+ # tl;dr
+ # - for non-inline unshare: Use BBB with pid_for_children, unless none/none
+ # #then (AAA) returned
+ # - for inline unshare: use returned pid (AAA) with pid_for_children
+ #
+ # All commands use unet.popen to launch the unshare of mutini or cat.
+ # mutini for PID unshare, otherwise cat. AAA is the returned pid BBB is the
+ # child of the returned.
+ #
+ # Unshare Variant
+ # ---------------
+ #
+ # Here we are running mutini if we are creating new pid namespace workspace,
+ # cat otherwise.
+ #
+ # [PID+PID] pid tree looks like this:
+ #
+ # PID NSPID PPID PGID
+ # uuu - N/A uuu main unet process
+ # AAA - uuu AAA nsenter (forking, from unet) (in unet namespaces -pid)
+ # BBB - AAA AAA unshare --fork --kill-child (forking)
+ # CCC 1 BBB CCC mutini (non-forking since it is pid 1 in new namespace)
+ #
+ # Use BBB if we use pid_for_children, CCC for all
+ #
+ # [PID+none] For non-pid workspace creation (but unet pid) we use cat and pid
+ # tree looks like this:
+ #
+ # PID PPID PGID
+ # uuu N/A uuu main unet process
+ # AAA uuu AAA nsenter (forking) (in unet namespaces -pid)
+ # BBB AAA AAA unshare -> cat (from unshare non-forking)
+ #
+ # Use BBB for all
+ #
+ # [none+PID] For pid workspace creation (but NOT unet pid) we use mutini and pid
+ # tree looks like this:
+ #
+ # PID NSPID PPID PGID
+ # uuu - N/A uuu main unet process
+ # AAA - uuu AAA nsenter -> unshare --fork --kill-child
+ # BBB 1 AAA AAA mutini (non-forking since it is pid 1 in new namespace)
+ #
+ # Use AAA if we use pid_for_children, BBB for all
+ #
+ # [none+none] For non-pid workspace and non-pid unet we use cat and pid tree
+ # looks like this:
+ #
+ # PID PPID PGID
+ # uuu N/A uuu main unet process
+ # AAA uuu AAA nsenter -> unshare -> cat
+ #
+ # Use AAA for all, there's no BBB
+ #
+ # Inline-Unshare Variant
+ # ----------------------
+ #
+ # For unshare_inline and new PID namespace we have unshared all but our PID
+ # namespace, but our children end up in the new namespace so the fork popen
+ # does is good enough.
+ #
+ # [PID+PID] pid tree looks like this:
+ #
+ # PID NSPID PPID PGID
+ # uuu - N/A uuu main unet process
+ # AAA - uuu AAA unshare --fork --kill-child (forking)
+ # BBB 1 AAA BBB mutini
+ #
+ # Use AAA if we use pid_for_children, BBB for all
+ #
+ # [PID+none] For non-pid workspace creation (but unet pid) we use cat and pid
+ # tree looks like this:
+ #
+ # PID PPID PGID
+ # uuu N/A uuu main unet process
+ # AAA uuu AAA unshare -> cat
+ #
+ # Use AAA for all
+ #
+ # [none+PID] For pid workspace creation (but NOT unet pid) we use mutini and pid
+ # tree looks like this:
+ #
+ # PID NSPID PPID PGID
+ # uuu - N/A uuu main unet process
+ # AAA - uuu AAA unshare --fork --kill-child
+ # BBB 1 AAA BBB mutini
+ #
+ # Use AAA if we use pid_for_children, BBB for all
+ #
+ # [none+none] For non-pid workspace and non-pid unet we use cat and pid tree
+ # looks like this:
+ #
+ # PID PPID PGID
+ # uuu N/A uuu main unet process
+ # AAA uuu AAA unshare -> cat
+ #
+ # Use AAA for all.
+ #
+ #
+ # ---------------------------------------------------------------------------
+ # Breakdown for unet namespace creation, and what PID to use for __pre_cmd
+ # ---------------------------------------------------------------------------
+ #
+ # tl;dr: save returned PID or nothing.
+ # - for non-inline unshare: Use AAA with pid_for_children (returned pid)
+ # - for inline unshare: no __precmd as the fork in popen is enough.
+ #
+ # Use commander to launch the unshare mutini/cat (for PID/none
+ # workspace PID) for non-inline case. AAA is the returned pid BBB is the child
+ # of the returned.
+ #
+ # Unshare Variant
+ # ---------------
+ #
+ # Here we are running mutini if we are creating new pid namespace workspace,
+ # cat otherwise.
+ #
+ # [PID] for unet pid creation pid tree looks like this:
+ #
+ # PID NSPID PPID PGID
+ # uuu - N/A uuu main unet process
+ # AAA - uuu AAA unshare --fork --kill-child (forking)
+ # BBB 1 AAA BBB mutini
+ #
+ # Use AAA if we use pid_for_children, BBB for all
+ #
+ # [none] for unet non-pid, pid tree looks like this:
+ #
+ # PID PPID PGID
+ # uuu N/A uuu main unet process
+ # AAA uuu AAA unshare -> cat
+ #
+ # Use AAA for all
+ #
+ # Inline-Unshare Variant
+ # -----------------------
+ #
+ # For unshare_inline and new PID namespace we have unshared all but our PID
+ # namespace, but our children end up in the new namespace so the fork in popen
+ # does is good enough.
+ #
+ # [PID] for unet pid creation pid tree looks like this:
+ #
+ # PID NSPID PPID PGID
+ # uuu - N/A uuu main unet process
+ # AAA 1 uuu AAA mutini
+ #
+ # Save p / p.pid, but don't configure any nsenter, uneeded.
+ #
+ # Use nothing as the fork when doing a popen is enough to be in all the right
+ # namepsaces.
+ #
+ # [none] for unet non-pid, pid tree looks like this:
+ #
+ # PID PPID PGID
+ # uuu N/A uuu main unet process
+ #
+ # Nothing, no __pre_cmd.
+ #
+ #
+
+ self.ppid = os.getppid()
+ self.unshare_inline = unshare_inline
+ if unshare_inline:
+ assert unet is None
+ self.uflags = uflags
+ #
+ # Open file descriptors for current namespaces for later resotration.
+ #
+ try:
+ kversion = [int(x) for x in platform.release().split("-")[0].split(".")]
+ kvok = kversion[0] > 5 or (kversion[0] == 5 and kversion[1] >= 8)
+ except ValueError:
+ kvok = False
+ if (
+ not kvok
+ or sys.version_info[0] < 3
+ or (sys.version_info[0] == 3 and sys.version_info[1] < 9)
+ ):
+ # get list of namespace file descriptors before we unshare
+ self.p_ns_fds = []
+ self.p_ns_fnames = []
+ tmpflags = uflags
+ for i in range(0, 64):
+ v = 1 << i
+ if (tmpflags & v) == 0:
+ continue
+ tmpflags &= ~v
+ if v in linux.namespace_files:
+ path = os.path.join("/proc/self", linux.namespace_files[v])
+ if os.path.exists(path):
+ self.p_ns_fds.append(os.open(path, 0))
+ self.p_ns_fnames.append(f"{path} -> {os.readlink(path)}")
+ self.logger.debug(
+ "%s: saving old namespace fd %s (%s)",
+ self,
+ self.p_ns_fnames[-1],
+ self.p_ns_fds[-1],
+ )
+ if not tmpflags:
+ break
+ else:
+ self.p_ns_fds = None
+ self.p_ns_fnames = None
+ self.ppid_fd = linux.pidfd_open(self.ppid)
+
+ self.logger.debug(
+ "%s: unshare to new namespaces %s",
+ self,
+ linux.clone_flag_string(uflags),
+ )
+
+ linux.unshare(uflags)
+
+ if not pid:
+ p = None
+ self.pid = None
+ self.nsenter_fork = False
+ else:
+ # Need to fork to create the PID namespace, but we need to continue
+ # running from the parent so that things like pytest work. We'll execute
+ # a mutini process to manage the child init 1 duties.
+ #
+ # We (the parent pid) can no longer create threads, due to that being
+ # restricted by the kernel. See EINVAL in clone(2).
+ #
+ p = commander.popen(
+ [mutini_path, "-v"],
+ stdin=subprocess.PIPE,
+ stdout=stdout,
+ stderr=stderr,
+ text=True,
+ # new session/pgid so signals don't propagate
+ start_new_session=True,
+ shell=False,
+ )
+ self.pid = p.pid
+ self.nsenter_fork = False
+ else:
+ # Using cat and a stdin PIPE is nice as it will exit when we do. However,
+ # we also detach it from the pgid so that signals do not propagate to it.
+ # This is b/c it would exit early (e.g., ^C) then, at least the main munet
+ # proc which has no other processes like frr daemons running, will take the
+ # main network namespace with it, which will remove the bridges and the
+ # veth pair (because the bridge side veth is deleted).
+ self.logger.debug("%s: creating namespace process: %s", self, cmd)
+
+ # Use the parent unet process if we have one this will cause us to inherit
+ # the namespaces correctly even in the non-inline case.
+ parent = self.unet if self.unet else commander
+
+ p = parent.popen(
+ cmd,
+ stdin=subprocess.PIPE,
+ stdout=stdout,
+ stderr=stderr,
+ text=True,
+ start_new_session=not unet,
+ shell=False,
+ )
+
+ # The pid number returned is in the global pid namespace. For unshare_inline
+ # this can be unfortunate b/c our /proc has been remounted in our new pid
+ # namespace and won't contain global pid namespace pids. To solve for this
+ # we get all the pid values for the process below.
+
+ # See (A) above for when we need the child pid.
+ self.logger.debug("%s: namespace process: %s", self, proc_str(p))
+ self.pid = p.pid
+ if unet and unet.nsenter_fork:
+ assert not unet.unshare_inline
+ # Need child pid of p.pid
+ pgrep = unet.rootcmd.get_exec_path("pgrep")
+ # a sing fork was done
+ child_pid = unet.rootcmd.cmd_raises([pgrep, "-o", "-P", str(p.pid)])
+ self.pid = int(child_pid.strip())
+ self.logger.debug("%s: child of namespace process: %s", self, pid)
+
+ self.p = p
+
+ # Let's always have a valid value.
+ if self.pid is None:
+ self.pid = our_pid
+
+ #
+ # Let's find all our pids in the nested PID namespaces
+ #
+ if unet:
+ proc_path = unet.proc_path
+ else:
+ proc_path = self.proc_path if hasattr(self, "proc_path") else "/proc"
+ proc_path = f"{proc_path}/{self.pid}"
+
+ pid_status = open(f"{proc_path}/status", "r", encoding="ascii").read()
+ m = re.search(r"\nNSpid:((?:\t[0-9]+)+)\n", pid_status)
+ self.pids = [int(x) for x in m.group(1).strip().split("\t")]
+ assert self.pids[0] == self.pid
+
+ self.logger.debug("%s: namespace scoped pids: %s", self, self.pids)
+
+ # -----------------------------------------------
+ # Now let's wait until unshare completes it's job
+ # -----------------------------------------------
+ timeout = Timeout(30)
+ if self.pid is not None and self.pid != our_pid:
+ while (not p or not p.poll()) and not timeout.is_expired():
+ # check new namespace values against old (nsdict), unshare
+ # can actually take a bit to complete.
+ for fname in tuple(nslist):
+ # self.pid will be the global pid b/c we didn't unshare_inline
+ nspath = f"{proc_path}/ns/{fname}"
+ try:
+ nsf = os.readlink(nspath)
+ except OSError as error:
+ self.logger.debug(
+ "unswitched: error (ok) checking %s: %s", nspath, error
+ )
+ continue
+ if nsdict[fname] != nsf:
+ self.logger.debug(
+ "switched: original %s current %s", nsdict[fname], nsf
+ )
+ nslist.remove(fname)
+ elif unshare_inline:
+ logging.warning(
+ "unshare_inline not unshared %s == %s", nsdict[fname], nsf
+ )
+ else:
+ self.logger.debug(
+ "unswitched: current %s elapsed: %s", nsf, timeout.elapsed()
+ )
+ if not nslist:
+ self.logger.debug(
+ "all done waiting for unshare after %s", timeout.elapsed()
+ )
+ break
+
+ elapsed = int(timeout.elapsed())
+ if elapsed <= 3:
+ time_mod.sleep(0.1)
+ else:
+ self.logger.info(
+ "%s: unshare taking more than %ss: %s", self, elapsed, nslist
+ )
+ time_mod.sleep(1)
+
+ if p is not None and p.poll():
+ self.logger.error("%s: namespace process failed: %s", self, comm_error(p))
+ assert p.poll() is None, "unshare failed"
+
+ #
+ # Setup the pre-command to enter the target namespace from the running munet
+ # process using self.pid
+ #
+
+ if pid:
+ nsenter_fork = True
+ elif unet and unet.nsenter_fork:
+ # if unet created a pid namespace we need to enter it since we aren't
+ # entering a child pid namespace we created for the node. Otherwise
+ # we have a /proc remounted under unet, but our process is running in
+ # the root pid namepsace
+ nselm = "pid_for_children"
+ nsflags.append(f"--pid={pp / nselm}")
+ nsenter_fork = True
+ else:
+ # We dont need a fork.
+ nsflags.append("-F")
+ nsenter_fork = False
+
+ # Save nsenter values if running from root namespace
+ # we need this for the unshare_inline case when run externally (e.g., from
+ # within tmux server).
+ root_nsflags = [x.replace("%P%", str(self.pid)) for x in nsflags]
+ self.__root_base_pre_cmd = ["/usr/bin/nsenter", *root_nsflags]
+ self.__root_pre_cmd = list(self.__root_base_pre_cmd)
+
+ if unshare_inline:
+ assert unet is None
+ # We have nothing to do here since our process is now in the correct
+ # namespaces and children will inherit from us, even the PID namespace will
+ # be corrent b/c commands are run by first forking.
+ self.nsenter_fork = False
+ self.nsflags = []
+ self.__base_pre_cmd = []
+ else:
+ # We will use nsenter
+ self.nsenter_fork = nsenter_fork
+ self.nsflags = nsflags
+ self.__base_pre_cmd = list(self.__root_base_pre_cmd)
+
+ self.__pre_cmd = list(self.__base_pre_cmd)
+
+ # Always mark new mount namespaces as recursive private
+ if mount:
+ # if self.p is None and not pid:
+ self.cmd_raises_nsonly("mount --make-rprivate /")
+
+ # We need to remount the procfs for the new PID namespace, since we aren't using
+ # unshare(1) which does that for us.
+ if pid and unshare_inline:
+ assert mount
+ self.cmd_raises_nsonly("mount -t proc proc /proc")
+
+ # We do not want cmd_status in child classes (e.g., container) for
+ # the remaining setup calls in this __init__ function.
+
+ if net:
+ # Remount /sys to pickup any changes in the network, but keep root
+ # /sys/fs/cgroup. This pattern could be made generic and supported for any
+ # overlapping mounts
+ if mount:
+ tmpmnt = f"/tmp/cgm-{self.pid}"
+ self.cmd_status_nsonly(
+ f"mkdir {tmpmnt} && mount --rbind /sys/fs/cgroup {tmpmnt}"
+ )
+ rc = o = e = None
+ for i in range(0, 10):
+ rc, o, e = self.cmd_status_nsonly(
+ "mount -t sysfs sysfs /sys", warn=False
+ )
+ if not rc:
+ break
+ self.logger.debug(
+ "got error mounting new sysfs will retry: %s",
+ cmd_error(rc, o, e),
+ )
+ time_mod.sleep(1)
+ else:
+ raise Exception(cmd_error(rc, o, e))
+
+ self.cmd_status_nsonly(
+ f"mount --move {tmpmnt} /sys/fs/cgroup && rmdir {tmpmnt}"
+ )
+
+ # Original micronet code
+ # self.cmd_raises_nsonly("mount -t sysfs sysfs /sys")
+ # self.cmd_raises_nsonly(
+ # "mount -o rw,nosuid,nodev,noexec,relatime "
+ # "-t cgroup2 cgroup /sys/fs/cgroup"
+ # )
+
+ # Set the hostname to the namespace name
+ if uts and set_hostname:
+ self.cmd_status_nsonly("hostname " + self.name)
+ nroot = subprocess.check_output("hostname")
+ if unshare_inline or (unet and unet.unshare_inline):
+ assert (
+ root_hostname != nroot
+ ), f'hostname unchanged from "{nroot}" wanted "{self.name}"'
+ else:
+ # Assert that we didn't just change the host hostname
+ assert (
+ root_hostname == nroot
+ ), f'root hostname "{root_hostname}" changed to "{nroot}"!'
+
+ if private_mounts:
+ if isinstance(private_mounts, str):
+ private_mounts = [private_mounts]
+ for m in private_mounts:
+ s = m.split(":", 1)
+ if len(s) == 1:
+ self.tmpfs_mount(s[0])
+ else:
+ self.bind_mount(s[0], s[1])
+
+ # this will fail if running inside the namespace with PID
+ if pid:
+ o = self.cmd_nostatus_nsonly("ls -l /proc/1/ns")
+ else:
+ o = self.cmd_nostatus_nsonly("ls -l /proc/self/ns")
+
+ self.logger.debug("namespaces:\n %s", o)
+
+ # will cache the path, which is important in delete to avoid running a shell
+ # which can hang during cleanup
+ self.ip_path = get_exec_path_host("ip")
+ if net:
+ self.cmd_status_nsonly([self.ip_path, "link", "set", "lo", "up"])
+
+ self.logger.info("%s: created", self)
+
+ def _get_pre_cmd(self, use_str, use_pty, ns_only=False, root_level=False, **kwargs):
+ """Get the pre-user-command values.
+
+ The values returned here should be what is required to cause the user's command
+ to execute in the correct context (e.g., namespace, container, sshremote).
+ """
+ del kwargs
+ del ns_only
+ del use_pty
+ pre_cmd = self.__root_pre_cmd if root_level else self.__pre_cmd
+ return shlex.join(pre_cmd) if use_str else list(pre_cmd)
+
+ def tmpfs_mount(self, inner):
+ self.logger.debug("Mounting tmpfs on %s", inner)
+ self.cmd_raises("mkdir -p " + inner)
+ self.cmd_raises("mount -n -t tmpfs tmpfs " + inner)
+
+ def bind_mount(self, outer, inner):
+ self.logger.debug("Bind mounting %s on %s", outer, inner)
+ if commander.test("-f", outer):
+ self.cmd_raises(f"mkdir -p {os.path.dirname(inner)} && touch {inner}")
+ else:
+ if not commander.test("-e", outer):
+ commander.cmd_raises_nsonly(f"mkdir -p {outer}")
+ self.cmd_raises(f"mkdir -p {inner}")
+ self.cmd_raises("mount --rbind {} {} ".format(outer, inner))
+
+ def add_netns(self, ns):
+ self.logger.debug("Adding network namespace %s", ns)
+
+ if os.path.exists("/run/netns/{}".format(ns)):
+ self.logger.warning("%s: Removing existing nsspace %s", self, ns)
+ try:
+ self.delete_netns(ns)
+ except Exception as ex:
+ self.logger.warning(
+ "%s: Couldn't remove existing nsspace %s: %s",
+ self,
+ ns,
+ str(ex),
+ exc_info=True,
+ )
+ self.cmd_raises_nsonly([self.ip_path, "netns", "add", ns])
+
+ def delete_netns(self, ns):
+ self.logger.debug("Deleting network namespace %s", ns)
+ self.cmd_raises_nsonly([self.ip_path, "netns", "delete", ns])
+
+ def set_intf_netns(self, intf, ns, up=False):
+ # In case a user hard-codes 1 thinking it "resets"
+ ns = str(ns)
+ if ns == "1":
+ ns = str(self.pid)
+
+ self.logger.debug("Moving interface %s to namespace %s", intf, ns)
+
+ cmd = [self.ip_path, "link", "set", intf, "netns", ns]
+ if up:
+ cmd.append("up")
+ self.intf_ip_cmd(intf, cmd)
+ if ns == str(self.pid):
+ # If we are returning then remove from dict
+ if intf in self.ifnetns:
+ del self.ifnetns[intf]
+ else:
+ self.ifnetns[intf] = ns
+
+ def reset_intf_netns(self, intf):
+ self.logger.debug("Moving interface %s to default namespace", intf)
+ self.set_intf_netns(intf, str(self.pid))
+
+ def intf_ip_cmd(self, intf, cmd):
+ """Run an ip command, considering an interface's possible namespace."""
+ if intf in self.ifnetns:
+ if isinstance(cmd, list):
+ assert cmd[0].endswith("ip")
+ cmd[1:1] = ["-n", self.ifnetns[intf]]
+ else:
+ assert cmd.startswith("ip ")
+ cmd = "ip -n " + self.ifnetns[intf] + cmd[2:]
+ self.cmd_raises_nsonly(cmd)
+
+ def intf_tc_cmd(self, intf, cmd):
+ """Run a tc command, considering an interface's possible namespace."""
+ if intf in self.ifnetns:
+ if isinstance(cmd, list):
+ assert cmd[0].endswith("tc")
+ cmd[1:1] = ["-n", self.ifnetns[intf]]
+ else:
+ assert cmd.startswith("tc ")
+ cmd = "tc -n " + self.ifnetns[intf] + cmd[2:]
+ self.cmd_raises_nsonly(cmd)
+
+ def set_ns_cwd(self, cwd: Union[str, Path]):
+ """Common code for changing pre_cmd and pre_nscmd."""
+ self.logger.debug("%s: new CWD %s", self, cwd)
+ self.__root_pre_cmd = self.__root_base_pre_cmd + ["--wd=" + str(cwd)]
+ if self.__pre_cmd:
+ self.__pre_cmd = self.__base_pre_cmd + ["--wd=" + str(cwd)]
+ elif self.unshare_inline:
+ os.chdir(cwd)
+
+ async def _async_delete(self):
+ if type(self) == LinuxNamespace: # pylint: disable=C0123
+ self.logger.info("%s: deleting", self)
+ else:
+ self.logger.debug("%s: LinuxNamespace sub-class deleting", self)
+
+ # Signal pid namespace proc to exit
+ if (
+ (self.p is None or self.p.pid != self.pid)
+ and self.pid
+ and self.pid != our_pid
+ ):
+ self.logger.debug(
+ "cleanup pid on separate pid %s from proc pid %s",
+ self.pid,
+ self.p.pid if self.p else None,
+ )
+ await self.cleanup_pid(self.pid)
+
+ if self.p is not None:
+ self.logger.debug("cleanup proc pid %s", self.p.pid)
+ await self.async_cleanup_proc(self.p)
+
+ # return to the previous namespace, need to do this in case anothe munet
+ # is being created, especially when it plans to inherit the parent's (host)
+ # namespace.
+ if self.uflags:
+ logging.info("restoring from inline unshare: cwd: %s", os.getcwd())
+ # This only works in linux>=5.8
+ if self.p_ns_fds is None:
+ self.logger.debug(
+ "%s: restoring namespaces %s",
+ self,
+ linux.clone_flag_string(self.uflags),
+ )
+ # fd = linux.pidfd_open(self.ppid)
+ fd = self.ppid_fd
+ retry = 3
+ for i in range(0, retry):
+ try:
+ linux.setns(fd, self.uflags)
+ except OSError as error:
+ self.logger.warning(
+ "%s: could not reset to old namespace fd %s: %s",
+ self,
+ fd,
+ error,
+ )
+ if i == retry - 1:
+ raise
+ time_mod.sleep(1)
+ os.close(fd)
+ else:
+ while self.p_ns_fds:
+ fd = self.p_ns_fds.pop()
+ fname = self.p_ns_fnames.pop()
+ self.logger.debug(
+ "%s: restoring namespace from fd %s (%s)", self, fname, fd
+ )
+ retry = 3
+ for i in range(0, retry):
+ try:
+ linux.setns(fd, 0)
+ break
+ except OSError as error:
+ self.logger.warning(
+ "%s: could not reset to old namespace fd %s (%s): %s",
+ self,
+ fname,
+ fd,
+ error,
+ )
+ if i == retry - 1:
+ raise
+ time_mod.sleep(1)
+ os.close(fd)
+ self.p_ns_fds = None
+ self.p_ns_fnames = None
+ logging.info("restored from unshare: cwd: %s", os.getcwd())
+
+ self.__root_base_pre_cmd = ["/bin/false"]
+ self.__base_pre_cmd = ["/bin/false"]
+ self.__root_pre_cmd = ["/bin/false"]
+ self.__pre_cmd = ["/bin/false"]
+
+ await super()._async_delete()
+
+
+class SharedNamespace(Commander):
+ """Share another namespace.
+
+ An object that executes commands in an existing pid's linux namespace
+ """
+
+ def __init__(self, name, pid=None, nsflags=None, **kwargs):
+ """Share a linux namespace.
+
+ Args:
+ name: Internal name for the namespace.
+ pid: PID of the process to share with.
+ nsflags: nsenter flags to pass to inherit namespaces from
+ """
+ super().__init__(name, **kwargs)
+
+ self.logger.debug("%s: Creating", self)
+
+ self.cwd = os.path.abspath(os.getcwd())
+ self.pid = pid if pid is not None else our_pid
+
+ nsflags = (x.replace("%P%", str(self.pid)) for x in nsflags) if nsflags else []
+ self.__base_pre_cmd = ["/usr/bin/nsenter", *nsflags] if nsflags else []
+ self.__pre_cmd = self.__base_pre_cmd
+ self.ip_path = self.get_exec_path("ip")
+
+ def _get_pre_cmd(self, use_str, use_pty, ns_only=False, root_level=False, **kwargs):
+ """Get the pre-user-command values.
+
+ The values returned here should be what is required to cause the user's command
+ to execute in the correct context (e.g., namespace, container, sshremote).
+ """
+ del kwargs
+ del ns_only
+ del use_pty
+ assert not root_level
+ return shlex.join(self.__pre_cmd) if use_str else list(self.__pre_cmd)
+
+ def set_ns_cwd(self, cwd: Union[str, Path]):
+ """Common code for changing pre_cmd and pre_nscmd."""
+ self.logger.debug("%s: new CWD %s", self, cwd)
+ self.__pre_cmd = self.__base_pre_cmd + ["--wd=" + str(cwd)]
+
+
+class Bridge(SharedNamespace, InterfaceMixin):
+ """A linux bridge."""
+
+ next_ord = 1
+
+ @classmethod
+ def _get_next_id(cls):
+ # Do not use `cls` here b/c that makes the variable class specific
+ n = Bridge.next_ord
+ Bridge.next_ord = n + 1
+ return n
+
+ def __init__(self, name=None, mtu=None, unet=None, **kwargs):
+ """Create a linux Bridge."""
+ self.id = self._get_next_id()
+ if not name:
+ name = "br{}".format(self.id)
+
+ unet_pid = our_pid if unet.pid is None else unet.pid
+
+ super().__init__(name, pid=unet_pid, nsflags=unet.nsflags, unet=unet, **kwargs)
+
+ self.set_intf_basename(self.name + "-e")
+
+ self.mtu = mtu
+
+ self.logger.debug("Bridge: Creating")
+
+ assert len(self.name) <= 16 # Make sure fits in IFNAMSIZE
+ self.cmd_raises(f"ip link delete {name} || true")
+ self.cmd_raises(f"ip link add {name} type bridge")
+ if self.mtu:
+ self.cmd_raises(f"ip link set {name} mtu {self.mtu}")
+ self.cmd_raises(f"ip link set {name} up")
+
+ self.logger.debug("%s: Created, Running", self)
+
+ def get_ifname(self, netname):
+ return self.net_intfs[netname] if netname in self.net_intfs else None
+
+ async def _async_delete(self):
+ """Stop the bridge (i.e., delete the linux resources)."""
+ if type(self) == Bridge: # pylint: disable=C0123
+ self.logger.info("%s: deleting", self)
+ else:
+ self.logger.debug("%s: Bridge sub-class deleting", self)
+
+ rc, o, e = await self.async_cmd_status(
+ [self.ip_path, "link", "show", self.name],
+ stdin=subprocess.DEVNULL,
+ start_new_session=True,
+ warn=False,
+ )
+ if not rc:
+ rc, o, e = await self.async_cmd_status(
+ [self.ip_path, "link", "delete", self.name],
+ stdin=subprocess.DEVNULL,
+ start_new_session=True,
+ warn=False,
+ )
+ if rc:
+ self.logger.error(
+ "%s: error deleting bridge %s: %s",
+ self,
+ self.name,
+ cmd_error(rc, o, e),
+ )
+ await super()._async_delete()
+
+
+class BaseMunet(LinuxNamespace):
+ """Munet."""
+
+ def __init__(
+ self,
+ name="munet",
+ isolated=True,
+ pid=True,
+ rundir=None,
+ pytestconfig=None,
+ **kwargs,
+ ):
+ """Create a Munet."""
+ # logging.warning("BaseMunet: %s", name)
+
+ self.hosts = {}
+ self.switches = {}
+ self.links = {}
+ self.macs = {}
+ self.rmacs = {}
+ self.isolated = isolated
+
+ self.cli_server = None
+ self.cli_sockpath = None
+ self.cli_histfile = None
+ self.cli_in_window_cmds = {}
+ self.cli_run_cmds = {}
+
+ #
+ # We need a directory for various files
+ #
+ if not rundir:
+ rundir = "/tmp/munet"
+ self.rundir = Path(rundir)
+
+ #
+ # Always having a global /proc is required to keep things from exploding
+ # complexity with nested new pid namespaces..
+ #
+ if pid:
+ self.proc_path = Path(tempfile.mkdtemp(suffix="-proc", prefix="mu-"))
+ logging.debug("%s: mounting /proc on proc_path %s", name, self.proc_path)
+ linux.mount("proc", str(self.proc_path), "proc")
+ else:
+ self.proc_path = Path("/proc")
+
+ #
+ # Now create a root level commander that works regardless of whether we inline
+ # unshare or not. Save it in the global variable as well
+ #
+
+ if not self.isolated:
+ self.rootcmd = commander
+ elif not pid:
+ nsflags = (
+ f"--mount={self.proc_path / '1/ns/mnt'}",
+ f"--net={self.proc_path / '1/ns/net'}",
+ f"--uts={self.proc_path / '1/ns/uts'}",
+ # f"--ipc={self.proc_path / '1/ns/ipc'}",
+ # f"--time={self.proc_path / '1/ns/time'}",
+ # f"--cgroup={self.proc_path / '1/ns/cgroup'}",
+ )
+ self.rootcmd = SharedNamespace("root", pid=1, nsflags=nsflags)
+ else:
+ # XXX user
+ nsflags = (
+ # XXX Backing up PID namespace just doesn't work.
+ # f"--pid={self.proc_path / '1/ns/pid_for_children'}",
+ f"--mount={self.proc_path / '1/ns/mnt'}",
+ f"--net={self.proc_path / '1/ns/net'}",
+ f"--uts={self.proc_path / '1/ns/uts'}",
+ # f"--ipc={self.proc_path / '1/ns/ipc'}",
+ # f"--time={self.proc_path / '1/ns/time'}",
+ # f"--cgroup={self.proc_path / '1/ns/cgroup'}",
+ )
+ self.rootcmd = SharedNamespace("root", pid=1, nsflags=nsflags)
+ global roothost # pylint: disable=global-statement
+
+ roothost = self.rootcmd
+
+ self.cfgopt = munet_config.ConfigOptionsProxy(pytestconfig)
+
+ super().__init__(
+ name, mount=True, net=isolated, uts=isolated, pid=pid, unet=None, **kwargs
+ )
+
+ # This allows us to cleanup any leftover running munet's
+ if "MUNET_PID" in os.environ:
+ if os.environ["MUNET_PID"] != str(our_pid):
+ logging.error(
+ "Found env MUNET_PID != our pid %s, instead its %s, changing",
+ our_pid,
+ os.environ["MUNET_PID"],
+ )
+ os.environ["MUNET_PID"] = str(our_pid)
+
+ # this is for testing purposes do not use
+ if not BaseMunet.g_unet:
+ BaseMunet.g_unet = self
+
+ self.logger.debug("%s: Creating", self)
+
+ def __getitem__(self, key):
+ if key in self.switches:
+ return self.switches[key]
+ return self.hosts[key]
+
+ def add_host(self, name, cls=LinuxNamespace, **kwargs):
+ """Add a host to munet."""
+ self.logger.debug("%s: add_host %s(%s)", self, cls.__name__, name)
+
+ self.hosts[name] = cls(name, unet=self, **kwargs)
+
+ # Create a new mounted FS for tracking nested network namespaces creatd by the
+ # user with `ip netns add`
+
+ # XXX why is this failing with podman???
+ # self.hosts[name].tmpfs_mount("/run/netns")
+
+ return self.hosts[name]
+
+ def add_link(self, node1, node2, if1, if2, mtu=None, **intf_constraints):
+ """Add a link between switch and node or 2 nodes.
+
+ If constraints are given they are applied to each endpoint. See
+ `InterfaceMixin::set_intf_constraints()` for more info.
+ """
+ isp2p = False
+
+ try:
+ name1 = node1.name
+ except AttributeError:
+ if node1 in self.switches:
+ node1 = self.switches[node1]
+ else:
+ node1 = self.hosts[node1]
+ name1 = node1.name
+
+ try:
+ name2 = node2.name
+ except AttributeError:
+ if node2 in self.switches:
+ node2 = self.switches[node2]
+ else:
+ node2 = self.hosts[node2]
+ name2 = node2.name
+
+ if name1 in self.switches:
+ assert name2 in self.hosts
+ elif name2 in self.switches:
+ assert name1 in self.hosts
+ name1, name2 = name2, name1
+ if1, if2 = if2, if1
+ else:
+ # p2p link
+ assert name1 in self.hosts
+ assert name2 in self.hosts
+ isp2p = True
+
+ lname = "{}:{}-{}:{}".format(name1, if1, name2, if2)
+ self.logger.debug("%s: add_link %s%s", self, lname, " p2p" if isp2p else "")
+ self.links[lname] = (name1, if1, name2, if2)
+
+ # And create the veth now.
+ if isp2p:
+ lhost, rhost = self.hosts[name1], self.hosts[name2]
+ lifname = "i1{:x}".format(lhost.pid)
+
+ # Done at root level
+ nsif1 = lhost.get_ns_ifname(if1)
+ nsif2 = rhost.get_ns_ifname(if2)
+
+ # Use pids[-1] to get the unet scoped pid for hosts
+ self.cmd_raises_nsonly(
+ f"ip link add {lifname} type veth peer name {nsif2}"
+ f" netns {rhost.pids[-1]}"
+ )
+ self.cmd_raises_nsonly(f"ip link set {lifname} netns {lhost.pids[-1]}")
+
+ lhost.cmd_raises_nsonly("ip link set {} name {}".format(lifname, nsif1))
+ if mtu:
+ lhost.cmd_raises_nsonly("ip link set {} mtu {}".format(nsif1, mtu))
+ lhost.cmd_raises_nsonly("ip link set {} up".format(nsif1))
+ lhost.register_interface(if1)
+
+ if mtu:
+ rhost.cmd_raises_nsonly("ip link set {} mtu {}".format(nsif2, mtu))
+ rhost.cmd_raises_nsonly("ip link set {} up".format(nsif2))
+ rhost.register_interface(if2)
+ else:
+ switch = self.switches[name1]
+ rhost = self.hosts[name2]
+
+ nsif1 = switch.get_ns_ifname(if1)
+ nsif2 = rhost.get_ns_ifname(if2)
+
+ if mtu is None:
+ mtu = switch.mtu
+
+ if len(nsif1) > 16:
+ self.logger.error('"%s" len %s > 16', nsif1, len(nsif1))
+ elif len(nsif2) > 16:
+ self.logger.error('"%s" len %s > 16', nsif2, len(nsif2))
+ assert len(nsif1) <= 16 and len(nsif2) <= 16 # Make sure fits in IFNAMSIZE
+
+ self.logger.debug("%s: Creating veth pair for link %s", self, lname)
+
+ # Use pids[-1] to get the unet scoped pid for hosts
+ # switch is already in our namespace so nothing to convert.
+ self.cmd_raises_nsonly(
+ f"ip link add {nsif1} type veth peer name {nsif2}"
+ f" netns {rhost.pids[-1]}"
+ )
+
+ if mtu:
+ # if switch.mtu:
+ # # the switch interface should match the switch config
+ # switch.cmd_raises_nsonly(
+ # "ip link set {} mtu {}".format(if1, switch.mtu)
+ # )
+ switch.cmd_raises_nsonly("ip link set {} mtu {}".format(nsif1, mtu))
+ rhost.cmd_raises_nsonly("ip link set {} mtu {}".format(nsif2, mtu))
+
+ switch.register_interface(if1)
+ rhost.register_interface(if2)
+ rhost.register_network(switch.name, if2)
+
+ switch.cmd_raises_nsonly(f"ip link set {nsif1} master {switch.name}")
+
+ switch.cmd_raises_nsonly(f"ip link set {nsif1} up")
+ rhost.cmd_raises_nsonly(f"ip link set {nsif2} up")
+
+ # Cache the MAC values, and reverse mapping
+ self.get_mac(name1, nsif1)
+ self.get_mac(name2, nsif2)
+
+ # Setup interface constraints if provided
+ if intf_constraints:
+ node1.set_intf_constraints(if1, **intf_constraints)
+ node2.set_intf_constraints(if2, **intf_constraints)
+
+ def add_switch(self, name, cls=Bridge, **kwargs):
+ """Add a switch to munet."""
+ self.logger.debug("%s: add_switch %s(%s)", self, cls.__name__, name)
+ self.switches[name] = cls(name, unet=self, **kwargs)
+ return self.switches[name]
+
+ def get_mac(self, name, ifname):
+ if name in self.hosts:
+ dev = self.hosts[name]
+ else:
+ dev = self.switches[name]
+
+ nsifname = self.get_ns_ifname(ifname)
+
+ if (name, ifname) not in self.macs:
+ _, output, _ = dev.cmd_status_nsonly("ip -o link show " + nsifname)
+ m = re.match(".*link/(loopback|ether) ([0-9a-fA-F:]+) .*", output)
+ mac = m.group(2)
+ self.macs[(name, ifname)] = mac
+ self.rmacs[mac] = (name, ifname)
+
+ return self.macs[(name, ifname)]
+
+ async def _delete_link(self, lname):
+ rname, rif = self.links[lname][2:4]
+ host = self.hosts[rname]
+ nsrif = host.get_ns_ifname(rif)
+
+ self.logger.debug("%s: Deleting veth pair for link %s", self, lname)
+ rc, o, e = await host.async_cmd_status_nsonly(
+ [self.ip_path, "link", "delete", nsrif],
+ stdin=subprocess.DEVNULL,
+ start_new_session=True,
+ warn=False,
+ )
+ if rc:
+ self.logger.error("Err del veth pair %s: %s", lname, cmd_error(rc, o, e))
+
+ async def _delete_links(self):
+ # for x in self.links:
+ # await self._delete_link(x)
+ return await asyncio.gather(*[self._delete_link(x) for x in self.links])
+
+ async def _async_delete(self):
+ """Delete the munet topology."""
+ # logger = self.logger if False else logging
+ logger = self.logger
+ if type(self) == BaseMunet: # pylint: disable=C0123
+ logger.info("%s: deleting.", self)
+ else:
+ logger.debug("%s: BaseMunet sub-class deleting.", self)
+
+ logger.debug("Deleting links")
+ try:
+ await self._delete_links()
+ except Exception as error:
+ logger.error("%s: error deleting links: %s", self, error, exc_info=True)
+
+ logger.debug("Deleting hosts and bridges")
+ try:
+ # Delete hosts and switches, wait for them all to complete
+ # even if there is an exception.
+ htask = [x.async_delete() for x in self.hosts.values()]
+ stask = [x.async_delete() for x in self.switches.values()]
+ await asyncio.gather(*htask, *stask, return_exceptions=True)
+ except Exception as error:
+ logger.error(
+ "%s: error deleting hosts and switches: %s", self, error, exc_info=True
+ )
+
+ self.links = {}
+ self.hosts = {}
+ self.switches = {}
+
+ try:
+ if self.cli_server:
+ self.cli_server.cancel()
+ self.cli_server = None
+ if self.cli_sockpath:
+ await self.async_cmd_status(
+ "rm -rf " + os.path.dirname(self.cli_sockpath)
+ )
+ self.cli_sockpath = None
+ except Exception as error:
+ logger.error(
+ "%s: error cli server or sockpaths: %s", self, error, exc_info=True
+ )
+
+ try:
+ if self.cli_histfile:
+ readline.write_history_file(self.cli_histfile)
+ self.cli_histfile = None
+ except Exception as error:
+ logger.error(
+ "%s: error saving history file: %s", self, error, exc_info=True
+ )
+
+ # XXX for some reason setns during the delete is changing our dir to /.
+ cwd = os.getcwd()
+
+ try:
+ await super()._async_delete()
+ except Exception as error:
+ logger.error(
+ "%s: error deleting parent classes: %s", self, error, exc_info=True
+ )
+ os.chdir(cwd)
+
+ try:
+ if self.proc_path and str(self.proc_path) != "/proc":
+ logger.debug("%s: umount, remove proc_path %s", self, self.proc_path)
+ linux.umount(str(self.proc_path), 0)
+ os.rmdir(self.proc_path)
+ except Exception as error:
+ logger.warning(
+ "%s: error umount and removing proc_path %s: %s",
+ self,
+ self.proc_path,
+ error,
+ exc_info=True,
+ )
+ try:
+ linux.umount(str(self.proc_path), linux.MNT_DETACH)
+ except Exception as error2:
+ logger.error(
+ "%s: error umount with detach proc_path %s: %s",
+ self,
+ self.proc_path,
+ error2,
+ exc_info=True,
+ )
+
+ if BaseMunet.g_unet == self:
+ BaseMunet.g_unet = None
+
+
+BaseMunet.g_unet = None
+
+if True: # pylint: disable=using-constant-test
+
+ class ShellWrapper:
+ """A Read-Execute-Print-Loop (REPL) interface.
+
+ A newline or prompt changing command should be sent to the
+ spawned child prior to creation as the `prompt` will be `expect`ed
+ """
+
+ def __init__(
+ self,
+ spawn,
+ prompt,
+ continuation_prompt=None,
+ extra_init_cmd=None,
+ will_echo=False,
+ escape_ansi=False,
+ ):
+ self.echo = will_echo
+ self.escape = (
+ re.compile(r"(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]") if escape_ansi else None
+ )
+
+ logging.debug(
+ 'ShellWraper: XXX prompt "%s" will_echo %s child.echo %s',
+ prompt,
+ will_echo,
+ spawn.echo,
+ )
+
+ self.child = spawn
+ if self.child.echo:
+ logging.info("Setting child to echo")
+ self.child.setecho(False)
+ self.child.waitnoecho()
+ assert not self.child.echo
+
+ self.prompt = prompt
+ self.cont_prompt = continuation_prompt
+
+ # Use expect_exact if we can as it should be faster
+ self.expects = [prompt]
+ if re.escape(prompt) == prompt and hasattr(self.child, "expect_exact"):
+ self._expectf = self.child.expect_exact
+ else:
+ self._expectf = self.child.expect
+ if continuation_prompt:
+ self.expects.append(continuation_prompt)
+ if re.escape(continuation_prompt) != continuation_prompt:
+ self._expectf = self.child.expect
+
+ if extra_init_cmd:
+ self.expect_prompt()
+ self.child.sendline(extra_init_cmd)
+ self.expect_prompt()
+
+ def expect_prompt(self, timeout=-1):
+ return self._expectf(self.expects, timeout=timeout)
+
+ def run_command(self, command, timeout=-1):
+ """Pexpect REPLWrapper compatible run_command.
+
+ This will split `command` into lines and feed each one to the shell.
+
+ Args:
+ command: string of commands separated by newlines, a trailing
+ newline will cause and empty line to be sent.
+ timeout: pexpect timeout value.
+ """
+ lines = command.splitlines()
+ if command[-1] == "\n":
+ lines.append("")
+ output = ""
+ index = 0
+ for line in lines:
+ self.child.sendline(line)
+ index = self.expect_prompt(timeout=timeout)
+ output += self.child.before
+
+ if index:
+ if hasattr(self.child, "kill"):
+ self.child.kill(signal.SIGINT)
+ else:
+ self.child.send("\x03")
+ self.expect_prompt(timeout=30 if self.child.timeout is None else -1)
+ raise ValueError("Continuation prompt found at end of commands")
+
+ if self.escape:
+ output = self.escape.sub("", output)
+
+ return output
+
+ def cmd_nostatus(self, cmd, timeout=-1):
+ r"""Execute a shell command.
+
+ Returns:
+ (strip/cleaned \r) output
+ """
+ output = self.run_command(cmd, timeout)
+ output = output.replace("\r\n", "\n")
+ if self.echo:
+ # remove the command
+ idx = output.find(cmd)
+ if idx == -1:
+ logging.warning(
+ "Didn't find command ('%s') in expected output ('%s')",
+ cmd,
+ output,
+ )
+ else:
+ # Remove up to and including the command from the output stream
+ output = output[idx + len(cmd) :]
+
+ return output.replace("\r", "").strip()
+
+ def cmd_status(self, cmd, timeout=-1):
+ r"""Execute a shell command.
+
+ Returns:
+ status and (strip/cleaned \r) output
+ """
+ # Run the command getting the output
+ output = self.cmd_nostatus(cmd, timeout)
+
+ # Now get the status
+ scmd = "echo $?"
+ rcstr = self.run_command(scmd)
+ rcstr = rcstr.replace("\r\n", "\n")
+ if self.echo:
+ # remove the command
+ idx = rcstr.find(scmd)
+ if idx == -1:
+ if self.echo:
+ logging.warning(
+ "Didn't find status ('%s') in expected output ('%s')",
+ scmd,
+ rcstr,
+ )
+ try:
+ rc = int(rcstr)
+ except Exception:
+ rc = 255
+ else:
+ rcstr = rcstr[idx + len(scmd) :].strip()
+ try:
+ rc = int(rcstr)
+ except ValueError as error:
+ logging.error(
+ "%s: error with expected status output: %s: %s",
+ self,
+ error,
+ rcstr,
+ exc_info=True,
+ )
+ rc = 255
+ return rc, output
+
+ def cmd_raises(self, cmd, timeout=-1):
+ r"""Execute a shell command.
+
+ Returns:
+ (strip/cleaned \r) ouptut
+
+ Raises:
+ CalledProcessError: on non-zero exit status
+ """
+ rc, output = self.cmd_status(cmd, timeout)
+ if rc:
+ raise CalledProcessError(rc, cmd, output)
+ return output
+
+
+# ---------------------------
+# Root level utility function
+# ---------------------------
+
+
+def get_exec_path(binary):
+ return commander.get_exec_path(binary)
+
+
+def get_exec_path_host(binary):
+ return commander.get_exec_path(binary)
+
+
+def get_our_script_path(script):
+ # would be nice to find this w/o using a path lookup
+ sdir = os.path.dirname(os.path.abspath(__file__))
+ spath = os.path.join(sdir, script)
+ if os.path.exists(spath):
+ return spath
+ return get_exec_path(script)
+
+
+commander = Commander("munet")
+roothost = None
diff --git a/tests/topotests/munet/cleanup.py b/tests/topotests/munet/cleanup.py
new file mode 100644
index 0000000000..c641cda685
--- /dev/null
+++ b/tests/topotests/munet/cleanup.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# September 30 2021, Christian Hopps <chopps@labn.net>
+#
+# Copyright 2021, LabN Consulting, L.L.C.
+#
+"""Provides functionality to cleanup processes on posix systems."""
+import glob
+import logging
+import os
+import signal
+
+
+def get_pids_with_env(has_var, has_val=None):
+ result = {}
+ for pidenv in glob.iglob("/proc/*/environ"):
+ pid = pidenv.split("/")[2]
+ try:
+ with open(pidenv, "rb") as rfb:
+ envlist = [
+ x.decode("utf-8").split("=", 1) for x in rfb.read().split(b"\0")
+ ]
+ envlist = [[x[0], ""] if len(x) == 1 else x for x in envlist]
+ envdict = dict(envlist)
+ if has_var not in envdict:
+ continue
+ if has_val is None:
+ result[pid] = envdict
+ elif envdict[has_var] == str(has_val):
+ result[pid] = envdict
+ except Exception:
+ # E.g., process exited and files are gone
+ pass
+ return result
+
+
+def _kill_piddict(pids_by_upid, sig):
+ ourpid = str(os.getpid())
+ for upid, pids in pids_by_upid:
+ logging.info("Sending %s to (%s) of munet pid %s", sig, ", ".join(pids), upid)
+ for pid in pids:
+ try:
+ if pid != ourpid:
+ cmdline = open(f"/proc/{pid}/cmdline", "r", encoding="ascii").read()
+ cmdline = cmdline.replace("\x00", " ")
+ logging.info("killing proc %s (%s)", pid, cmdline)
+ os.kill(int(pid), sig)
+ except Exception:
+ pass
+
+
+def _get_our_pids():
+ ourpid = str(os.getpid())
+ piddict = get_pids_with_env("MUNET_PID", ourpid)
+ pids = [x for x in piddict if x != ourpid]
+ if pids:
+ return {ourpid: pids}
+ return {}
+
+
+def _get_other_pids():
+ piddict = get_pids_with_env("MUNET_PID")
+ unet_pids = {d["MUNET_PID"] for d in piddict.values()}
+ pids_by_upid = {p: set() for p in unet_pids}
+ for pid, envdict in piddict.items():
+ unet_pid = envdict["MUNET_PID"]
+ pids_by_upid[unet_pid].add(pid)
+ # Filter out any child pid sets whos munet pid is still running
+ return {x: y for x, y in pids_by_upid.items() if x not in y}
+
+
+def _get_pids_by_upid(ours):
+ if ours:
+ return _get_our_pids()
+ return _get_other_pids()
+
+
+def _cleanup_pids(ours):
+ pids_by_upid = _get_pids_by_upid(ours).items()
+ if not pids_by_upid:
+ return
+
+ t = "current" if ours else "previous"
+ logging.info("Reaping %s munet processes", t)
+
+ # _kill_piddict(pids_by_upid, signal.SIGTERM)
+
+ # # Give them 5 second to exit cleanly
+ # logging.info("Waiting up to 5s to allow for clean exit of abandon'd pids")
+ # for _ in range(0, 5):
+ # pids_by_upid = _get_pids_by_upid(ours).items()
+ # if not pids_by_upid:
+ # return
+ # time.sleep(1)
+
+ pids_by_upid = _get_pids_by_upid(ours).items()
+ _kill_piddict(pids_by_upid, signal.SIGKILL)
+
+
+def cleanup_current():
+ """Attempt to cleanup preview runs.
+
+ Currently this only scans for old processes.
+ """
+ _cleanup_pids(True)
+
+
+def cleanup_previous():
+ """Attempt to cleanup preview runs.
+
+ Currently this only scans for old processes.
+ """
+ _cleanup_pids(False)
diff --git a/tests/topotests/munet/cli.py b/tests/topotests/munet/cli.py
new file mode 100644
index 0000000000..f58ea99d67
--- /dev/null
+++ b/tests/topotests/munet/cli.py
@@ -0,0 +1,964 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# July 24 2021, Christian Hopps <chopps@labn.net>
+#
+# Copyright 2021, LabN Consulting, L.L.C.
+#
+"""A module that implements a CLI."""
+import argparse
+import asyncio
+import functools
+import logging
+import multiprocessing
+import os
+import pty
+import re
+import readline
+import select
+import shlex
+import socket
+import subprocess
+import sys
+import tempfile
+import termios
+import tty
+
+
+try:
+ from . import linux
+ from .config import list_to_dict_with_key
+except ImportError:
+ # We cannot use relative imports and still run this module directly as a script, and
+ # there are some use cases where we want to run this file as a script.
+ sys.path.append(os.path.dirname(os.path.realpath(__file__)))
+ import linux
+
+ from config import list_to_dict_with_key
+
+
+ENDMARKER = b"\x00END\x00"
+
+logger = logging.getLogger(__name__)
+
+
+def lineiter(sock):
+ s = ""
+ while True:
+ sb = sock.recv(256)
+ if not sb:
+ return
+
+ s += sb.decode("utf-8")
+ i = s.find("\n")
+ if i != -1:
+ yield s[:i]
+ s = s[i + 1 :]
+
+
+# Would be nice to convert to async, but really not needed as used
+def spawn(unet, host, cmd, iow, ns_only):
+ if sys.stdin.isatty():
+ old_tty = termios.tcgetattr(sys.stdin)
+ tty.setraw(sys.stdin.fileno())
+
+ try:
+ master_fd, slave_fd = pty.openpty()
+
+ ns = unet.hosts[host] if host and host != unet else unet
+ popenf = ns.popen_nsonly if ns_only else ns.popen
+
+ # use os.setsid() make it run in a new process group, or bash job
+ # control will not be enabled
+ p = popenf(
+ cmd,
+ # _common_prologue, later in call chain, only does this for use_pty == False
+ preexec_fn=os.setsid,
+ stdin=slave_fd,
+ stdout=slave_fd,
+ stderr=slave_fd,
+ universal_newlines=True,
+ use_pty=True,
+ # XXX this is actually implementing "run on host" for real
+ # skip_pre_cmd=ns_only,
+ )
+ iow.write("\r")
+ iow.flush()
+
+ while p.poll() is None:
+ r, _, _ = select.select([sys.stdin, master_fd], [], [], 0.25)
+ if sys.stdin in r:
+ d = os.read(sys.stdin.fileno(), 10240)
+ os.write(master_fd, d)
+ elif master_fd in r:
+ o = os.read(master_fd, 10240)
+ if o:
+ iow.write(o.decode("utf-8"))
+ iow.flush()
+ finally:
+ # restore tty settings back
+ if sys.stdin.isatty():
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
+
+
+def is_host_regex(restr):
+ return len(restr) > 2 and restr[0] == "/" and restr[-1] == "/"
+
+
+def get_host_regex(restr):
+ if len(restr) < 3 or restr[0] != "/" or restr[-1] != "/":
+ return None
+ return re.compile(restr[1:-1])
+
+
+def host_in(restr, names):
+ """Determine if matcher is a regex that matches one of names."""
+ if not (regexp := get_host_regex(restr)):
+ return restr in names
+ for name in names:
+ if regexp.fullmatch(name):
+ return True
+ return False
+
+
+def expand_host(restr, names):
+ """Expand name or regexp into list of hosts."""
+ hosts = []
+ regexp = get_host_regex(restr)
+ if not regexp:
+ assert restr in names
+ hosts.append(restr)
+ else:
+ for name in names:
+ if regexp.fullmatch(name):
+ hosts.append(name)
+ return sorted(hosts)
+
+
+def expand_hosts(restrs, names):
+ """Expand list of host names or regex into list of hosts."""
+ hosts = []
+ for restr in restrs:
+ hosts += expand_host(restr, names)
+ return sorted(hosts)
+
+
+def host_cmd_split(unet, line, toplevel):
+ all_hosts = set(unet.hosts)
+ csplit = line.split()
+ i = 0
+ banner = False
+ for i, e in enumerate(csplit):
+ if is_re := is_host_regex(e):
+ banner = True
+ if not host_in(e, all_hosts):
+ if not is_re:
+ break
+ else:
+ i += 1
+
+ if i == 0 and csplit and csplit[0] == "*":
+ hosts = sorted(all_hosts)
+ csplit = csplit[1:]
+ banner = True
+ elif i == 0 and csplit and csplit[0] == ".":
+ hosts = [unet]
+ csplit = csplit[1:]
+ else:
+ hosts = expand_hosts(csplit[:i], all_hosts)
+ csplit = csplit[i:]
+
+ if not hosts and not csplit[:i]:
+ if toplevel:
+ hosts = [unet]
+ else:
+ hosts = sorted(all_hosts)
+ banner = True
+
+ if not csplit:
+ return hosts, "", "", True
+
+ i = line.index(csplit[0])
+ i += len(csplit[0])
+ return hosts, csplit[0], line[i:].strip(), banner
+
+
+def win_cmd_host_split(unet, cmd, kinds, defall):
+ if kinds:
+ all_hosts = {
+ x for x in unet.hosts if unet.hosts[x].config.get("kind", "") in kinds
+ }
+ else:
+ all_hosts = set(unet.hosts)
+
+ csplit = cmd.split()
+ i = 0
+ for i, e in enumerate(csplit):
+ if not host_in(e, all_hosts):
+ if not is_host_regex(e):
+ break
+ else:
+ i += 1
+
+ if i == 0 and csplit and csplit[0] == "*":
+ hosts = sorted(all_hosts)
+ csplit = csplit[1:]
+ elif i == 0 and csplit and csplit[0] == ".":
+ hosts = [unet]
+ csplit = csplit[1:]
+ else:
+ hosts = expand_hosts(csplit[:i], all_hosts)
+
+ if not hosts and defall and not csplit[:i]:
+ hosts = sorted(all_hosts)
+
+ # Filter hosts based on cmd
+ cmd = " ".join(csplit[i:])
+ return hosts, cmd
+
+
+def proc_readline(fd, prompt, histfile):
+ """Read a line of input from user while running in a sub-process."""
+ # How do we change the command though, that's what's displayed in ps normally
+ linux.set_process_name("Munet CLI")
+ try:
+ # For some reason sys.stdin is fileno == 16 and useless
+ sys.stdin = os.fdopen(0)
+ histfile = init_history(None, histfile)
+ line = input(prompt)
+ readline.write_history_file(histfile)
+ if line is None:
+ os.write(fd, b"\n")
+ os.write(fd, bytes(f":{str(line)}\n", encoding="utf-8"))
+ except EOFError:
+ os.write(fd, b"\n")
+ except KeyboardInterrupt:
+ os.write(fd, b"I\n")
+ except Exception as error:
+ os.write(fd, bytes(f"E{str(error)}\n", encoding="utf-8"))
+
+
+async def async_input_reader(rfd):
+ """Read a line of input from the user input sub-process pipe."""
+ rpipe = os.fdopen(rfd, mode="r")
+ reader = asyncio.StreamReader()
+
+ def protocol_factory():
+ return asyncio.StreamReaderProtocol(reader)
+
+ loop = asyncio.get_event_loop()
+ transport, _ = await loop.connect_read_pipe(protocol_factory, rpipe)
+ o = await reader.readline()
+ transport.close()
+
+ o = o.decode("utf-8").strip()
+ if not o:
+ return None
+ if o[0] == "I":
+ raise KeyboardInterrupt()
+ if o[0] == "E":
+ raise Exception(o[1:])
+ assert o[0] == ":"
+ return o[1:]
+
+
+#
+# A lot of work to add async `input` handling without creating a thread. We cannot use
+# threads when unshare_inline is used with pid namespace per kernel clone(2)
+# restriction.
+#
+async def async_input(prompt, histfile):
+ """Asynchronously read a line from the user."""
+ rfd, wfd = os.pipe()
+ p = multiprocessing.Process(target=proc_readline, args=(wfd, prompt, histfile))
+ p.start()
+ logging.debug("started async_input input process: %s", p)
+ try:
+ return await async_input_reader(rfd)
+ finally:
+ logging.debug("joining async_input input process")
+ p.join()
+
+
+def make_help_str(unet):
+
+ w = sorted([x if x else "" for x in unet.cli_in_window_cmds])
+ ww = unet.cli_in_window_cmds
+ u = sorted([x if x else "" for x in unet.cli_run_cmds])
+ uu = unet.cli_run_cmds
+
+ s = (
+ """
+Basic Commands:
+ cli :: open a secondary CLI window
+ help :: this help
+ hosts :: list hosts
+ quit :: quit the cli
+
+ HOST can be a host or one of the following:
+ - '*' for all hosts
+ - '.' for the parent munet
+ - a regex specified between '/' (e.g., '/rtr.*/')
+
+New Window Commands:\n"""
+ + "\n".join([f" {ww[v][0]}\t:: {ww[v][1]}" for v in w])
+ + """\nInline Commands:\n"""
+ + "\n".join([f" {uu[v][0]}\t:: {uu[v][1]}" for v in u])
+ + "\n"
+ )
+ return s
+
+
+def get_shcmd(unet, host, kinds, execfmt, ucmd):
+ if host is None:
+ h = None
+ kind = None
+ elif host is unet or host == "":
+ h = unet
+ kind = ""
+ else:
+ h = unet.hosts[host]
+ kind = h.config.get("kind", "")
+ if kinds and kind not in kinds:
+ return ""
+ if not isinstance(execfmt, str):
+ execfmt = execfmt.get(kind, {}).get("exec", "")
+ if not execfmt:
+ return ""
+
+ # Do substitutions for {} in string
+ numfmt = len(re.findall(r"{\d*}", execfmt))
+ if numfmt > 1:
+ ucmd = execfmt.format(*shlex.split(ucmd))
+ elif numfmt:
+ ucmd = execfmt.format(ucmd)
+ elif len(re.findall(r"{[a-zA-Z_][0-9a-zA-Z_\.]*}", execfmt)):
+ if execfmt.endswith('"'):
+ fstring = "f'''" + execfmt + "'''"
+ else:
+ fstring = 'f"""' + execfmt + '"""'
+ ucmd = eval( # pylint: disable=W0123
+ fstring,
+ globals(),
+ {"host": h, "unet": unet, "user_input": ucmd},
+ )
+ else:
+ # No variable or usercmd substitution at all.
+ ucmd = execfmt
+
+ # Do substitution for munet variables
+ ucmd = ucmd.replace("%CONFIGDIR%", str(unet.config_dirname))
+ if host is None or host is unet:
+ ucmd = ucmd.replace("%RUNDIR%", str(unet.rundir))
+ return ucmd.replace("%NAME%", ".")
+ ucmd = ucmd.replace("%RUNDIR%", str(os.path.join(unet.rundir, host)))
+ if h.mgmt_ip:
+ ucmd = ucmd.replace("%IPADDR%", str(h.mgmt_ip))
+ elif h.mgmt_ip6:
+ ucmd = ucmd.replace("%IPADDR%", str(h.mgmt_ip6))
+ if h.mgmt_ip6:
+ ucmd = ucmd.replace("%IP6ADDR%", str(h.mgmt_ip6))
+ return ucmd.replace("%NAME%", str(host))
+
+
+async def run_command(
+ unet,
+ outf,
+ line,
+ execfmt,
+ banner,
+ hosts,
+ toplevel,
+ kinds,
+ ns_only=False,
+ interactive=False,
+):
+ """Runs a command on a set of hosts.
+
+ Runs `execfmt`. Prior to executing the string the following transformations are
+ performed on it.
+
+ `execfmt` may also be a dictionary of dicitonaries keyed on kind with `exec` holding
+ the kind's execfmt string.
+
+ - if `{}` is present then `str.format` is called to replace `{}` with any extra
+ input values after the command and hosts are removed from the input.
+ - else if any `{digits}` are present then `str.format` is called to replace
+ `{digits}` with positional args obtained from the addittional user input
+ first passed to `shlex.split`.
+ - else f-string style interpolation is performed on the string with
+ the local variables `host` (the current node object or None),
+ `unet` (the Munet object), and `user_input` (the additional command input)
+ defined.
+
+ The output is sent to `outf`. If `ns_only` is True then the `execfmt` is
+ run using `Commander.cmd_status_nsonly` otherwise it is run with
+ `Commander.cmd_status`.
+ """
+ if kinds:
+ logging.info("Filtering hosts to kinds: %s", kinds)
+ hosts = [x for x in hosts if unet.hosts[x].config.get("kind", "") in kinds]
+ logging.info("Filtered hosts: %s", hosts)
+
+ if not hosts:
+ if not toplevel:
+ return
+ hosts = [unet]
+
+ # if unknowns := [x for x in hosts if x not in unet.hosts]:
+ # outf.write("%% Unknown host[s]: %s\n" % ", ".join(unknowns))
+ # return
+
+ # if sys.stdin.isatty() and interactive:
+ if interactive:
+ for host in hosts:
+ shcmd = get_shcmd(unet, host, kinds, execfmt, line)
+ if not shcmd:
+ continue
+ if len(hosts) > 1 or banner:
+ outf.write(f"------ Host: {host} ------\n")
+ spawn(unet, host if not toplevel else unet, shcmd, outf, ns_only)
+ if len(hosts) > 1 or banner:
+ outf.write(f"------- End: {host} ------\n")
+ outf.write("\n")
+ return
+
+ aws = []
+ for host in hosts:
+ shcmd = get_shcmd(unet, host, kinds, execfmt, line)
+ if not shcmd:
+ continue
+ if toplevel:
+ ns = unet
+ else:
+ ns = unet.hosts[host] if host and host != unet else unet
+ if ns_only:
+ cmdf = ns.async_cmd_status_nsonly
+ else:
+ cmdf = ns.async_cmd_status
+ aws.append(cmdf(shcmd, warn=False, stderr=subprocess.STDOUT))
+
+ results = await asyncio.gather(*aws, return_exceptions=True)
+ for host, result in zip(hosts, results):
+ if isinstance(result, Exception):
+ o = str(result) + "\n"
+ rc = -1
+ else:
+ rc, o, _ = result
+ if len(hosts) > 1 or banner:
+ outf.write(f"------ Host: {host} ------\n")
+ if rc:
+ outf.write(f"*** non-zero exit status: {rc}\n")
+ outf.write(o)
+ if len(hosts) > 1 or banner:
+ outf.write(f"------- End: {host} ------\n")
+
+
+cli_builtins = ["cli", "help", "hosts", "quit"]
+
+
+class Completer:
+ """A completer class for the CLI."""
+
+ def __init__(self, unet):
+ self.unet = unet
+
+ def complete(self, text, state):
+ line = readline.get_line_buffer()
+ tokens = line.split()
+ # print(f"\nXXX: tokens: {tokens} text: '{text}' state: {state}'\n")
+
+ first_token = not tokens or (text and len(tokens) == 1)
+
+ # If we have already have a builtin command we are done
+ if tokens and tokens[0] in cli_builtins:
+ return [None]
+
+ cli_run_cmds = set(self.unet.cli_run_cmds.keys())
+ top_run_cmds = {x for x in cli_run_cmds if self.unet.cli_run_cmds[x][3]}
+ cli_run_cmds -= top_run_cmds
+ cli_win_cmds = set(self.unet.cli_in_window_cmds.keys())
+ hosts = set(self.unet.hosts.keys())
+ is_window_cmd = bool(tokens) and tokens[0] in cli_win_cmds
+ done_set = set()
+ if bool(tokens):
+ if text:
+ done_set = set(tokens[:-1])
+ else:
+ done_set = set(tokens)
+
+ # Determine the domain for completions
+ if not tokens or first_token:
+ all_cmds = (
+ set(cli_builtins) | hosts | cli_run_cmds | cli_win_cmds | top_run_cmds
+ )
+ elif is_window_cmd:
+ all_cmds = hosts
+ elif tokens and tokens[0] in top_run_cmds:
+ # nothing to complete if a top level command
+ pass
+ elif not bool(done_set & cli_run_cmds):
+ all_cmds = hosts | cli_run_cmds
+
+ if not text:
+ completes = all_cmds
+ else:
+ # print(f"\nXXX: all_cmds: {all_cmds} text: '{text}'\n")
+ completes = {x + " " for x in all_cmds if x.startswith(text)}
+
+ # print(f"\nXXX: completes: {completes} text: '{text}' state: {state}'\n")
+ # remove any completions already present
+ completes -= done_set
+ completes = sorted(completes) + [None]
+ return completes[state]
+
+
+async def doline(
+ unet, line, outf, background=False, notty=False
+): # pylint: disable=R0911
+
+ line = line.strip()
+ m = re.fullmatch(r"^(\S+)(?:\s+(.*))?$", line)
+ if not m:
+ return True
+
+ cmd = m.group(1)
+ nline = m.group(2) if m.group(2) else ""
+
+ if cmd in ("q", "quit"):
+ return False
+
+ if cmd == "help":
+ outf.write(make_help_str(unet))
+ return True
+ if cmd in ("h", "hosts"):
+ outf.write(f"% Hosts:\t{' '.join(sorted(unet.hosts.keys()))}\n")
+ return True
+ if cmd == "cli":
+ await remote_cli(
+ unet,
+ "secondary> ",
+ "Secondary CLI",
+ background,
+ )
+ return True
+
+ #
+ # In window commands
+ #
+
+ if cmd in unet.cli_in_window_cmds:
+ execfmt, toplevel, kinds, kwargs = unet.cli_in_window_cmds[cmd][2:]
+
+ # if toplevel:
+ # ucmd = " ".join(nline.split())
+ # else:
+ hosts, ucmd = win_cmd_host_split(unet, nline, kinds, False)
+ if not hosts:
+ if not toplevel:
+ return True
+ hosts = [unet]
+
+ if isinstance(execfmt, str):
+ found_brace = "{}" in execfmt
+ else:
+ found_brace = False
+ for d in execfmt.values():
+ if "{}" in d["exec"]:
+ found_brace = True
+ break
+ if not found_brace and ucmd and not toplevel:
+ # CLI command does not expect user command so treat as hosts of which some
+ # must be unknown
+ unknowns = [x for x in ucmd.split() if x not in unet.hosts]
+ outf.write(f"% Unknown host[s]: {' '.join(unknowns)}\n")
+ return True
+
+ try:
+ if not hosts and toplevel:
+ hosts = [unet]
+
+ for host in hosts:
+ shcmd = get_shcmd(unet, host, kinds, execfmt, ucmd)
+ if toplevel or host == unet:
+ unet.run_in_window(shcmd, **kwargs)
+ else:
+ unet.hosts[host].run_in_window(shcmd, **kwargs)
+ except Exception as error:
+ outf.write(f"% Error: {error}\n")
+ return True
+
+ #
+ # Inline commands
+ #
+
+ toplevel = unet.cli_run_cmds[cmd][3] if cmd in unet.cli_run_cmds else False
+ # if toplevel:
+ # logging.debug("top-level: cmd: '%s' nline: '%s'", cmd, nline)
+ # hosts = None
+ # banner = False
+ # else:
+
+ hosts, cmd, nline, banner = host_cmd_split(unet, line, toplevel)
+ hoststr = "munet" if hosts == [unet] else f"{hosts}"
+ logging.debug("hosts: '%s' cmd: '%s' nline: '%s'", hoststr, cmd, nline)
+
+ if cmd in unet.cli_run_cmds:
+ pass
+ elif "" in unet.cli_run_cmds:
+ nline = f"{cmd} {nline}"
+ cmd = ""
+ else:
+ outf.write(f"% Unknown command: {cmd} {nline}\n")
+ return True
+
+ execfmt, toplevel, kinds, ns_only, interactive = unet.cli_run_cmds[cmd][2:]
+ if interactive and notty:
+ outf.write("% Error: interactive command must be run from primary CLI\n")
+ return True
+
+ await run_command(
+ unet,
+ outf,
+ nline,
+ execfmt,
+ banner,
+ hosts,
+ toplevel,
+ kinds,
+ ns_only,
+ interactive,
+ )
+
+ return True
+
+
+async def cli_client(sockpath, prompt="munet> "):
+ """Implement the user-facing CLI for a remote munet reached by a socket."""
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.settimeout(10)
+ sock.connect(sockpath)
+
+ # Go into full non-blocking mode now
+ sock.settimeout(None)
+
+ print("\n--- Munet CLI Starting ---\n\n")
+ while True:
+ line = input(prompt)
+ if line is None:
+ return
+
+ # Need to put \n back
+ line += "\n"
+
+ # Send the CLI command
+ sock.send(line.encode("utf-8"))
+
+ def bendswith(b, sentinel):
+ slen = len(sentinel)
+ return len(b) >= slen and b[-slen:] == sentinel
+
+ # Collect the output
+ rb = b""
+ while not bendswith(rb, ENDMARKER):
+ lb = sock.recv(4096)
+ if not lb:
+ return
+ rb += lb
+
+ # Remove the marker
+ rb = rb[: -len(ENDMARKER)]
+
+ # Write the output
+ sys.stdout.write(rb.decode("utf-8"))
+
+
+async def local_cli(unet, outf, prompt, histfile, background):
+ """Implement the user-side CLI for local munet."""
+ assert unet is not None
+ completer = Completer(unet)
+ readline.parse_and_bind("tab: complete")
+ readline.set_completer(completer.complete)
+
+ print("\n--- Munet CLI Starting ---\n\n")
+ while True:
+ try:
+ line = await async_input(prompt, histfile)
+ if line is None:
+ return
+
+ if not await doline(unet, line, outf, background):
+ return
+ except KeyboardInterrupt:
+ outf.write("%% Caught KeyboardInterrupt\nUse ^D or 'quit' to exit")
+
+
+def init_history(unet, histfile):
+ try:
+ if histfile is None:
+ histfile = os.path.expanduser("~/.munet-history.txt")
+ if not os.path.exists(histfile):
+ if unet:
+ unet.cmd("touch " + histfile)
+ else:
+ subprocess.run("touch " + histfile, shell=True, check=True)
+ if histfile:
+ readline.read_history_file(histfile)
+ return histfile
+ except Exception as error:
+ logging.warning("init_history failed: %s", error)
+ return None
+
+
+async def cli_client_connected(unet, background, reader, writer):
+ """Handle CLI commands inside the munet process from a socket."""
+ # # Go into full non-blocking mode now
+ # client.settimeout(None)
+ logging.debug("cli client connected")
+ while True:
+ line = await reader.readline()
+ if not line:
+ logging.debug("client closed cli connection")
+ break
+ line = line.decode("utf-8").strip()
+
+ class EncodingFile:
+ """Wrap a writer to encode in utf-8."""
+
+ def __init__(self, writer):
+ self.writer = writer
+
+ def write(self, x):
+ self.writer.write(x.encode("utf-8"))
+
+ def flush(self):
+ self.writer.flush()
+
+ if not await doline(unet, line, EncodingFile(writer), background, notty=True):
+ logging.debug("server closing cli connection")
+ return
+
+ writer.write(ENDMARKER)
+ await writer.drain()
+
+
+async def remote_cli(unet, prompt, title, background):
+ """Open a CLI in a new window."""
+ try:
+ if not unet.cli_sockpath:
+ sockpath = os.path.join(tempfile.mkdtemp("-sockdir", "pty-"), "cli.sock")
+ ccfunc = functools.partial(cli_client_connected, unet, background)
+ s = await asyncio.start_unix_server(ccfunc, path=sockpath)
+ unet.cli_server = asyncio.create_task(s.serve_forever(), name="cli-task")
+ unet.cli_sockpath = sockpath
+ logging.info("server created on :\n%s\n", sockpath)
+
+ # Open a new window with a new CLI
+ python_path = await unet.async_get_exec_path(["python3", "python"])
+ us = os.path.realpath(__file__)
+ cmd = f"{python_path} {us}"
+ if unet.cli_histfile:
+ cmd += " --histfile=" + unet.cli_histfile
+ if prompt:
+ cmd += f" --prompt='{prompt}'"
+ cmd += " " + unet.cli_sockpath
+ unet.run_in_window(cmd, title=title, background=False)
+ except Exception as error:
+ logging.error("cli server: unexpected exception: %s", error)
+
+
+def add_cli_in_window_cmd(
+ unet, name, helpfmt, helptxt, execfmt, toplevel, kinds, **kwargs
+):
+ """Adds a CLI command to the CLI.
+
+ The command `cmd` is added to the commands executable by the user from the CLI. See
+ `base.Commander.run_in_window` for the arguments that can be passed in `args` and
+ `kwargs` to this function.
+
+ Args:
+ unet: unet object
+ name: command string (no spaces)
+ helpfmt: format of command to display in help (left side)
+ helptxt: help string for command (right side)
+ execfmt: interpreter `cmd` to pass to `host.run_in_window()`, if {} present then
+ allow for user commands to be entered and inserted. May also be a dict of dict
+ keyed on kind with sub-key of "exec" providing the `execfmt` string for that
+ kind.
+ toplevel: run command in common top-level namespaec not inside hosts
+ kinds: limit CLI command to nodes which match list of kinds.
+ **kwargs: keyword args to pass to `host.run_in_window()`
+ """
+ unet.cli_in_window_cmds[name] = (helpfmt, helptxt, execfmt, toplevel, kinds, kwargs)
+
+
+def add_cli_run_cmd(
+ unet,
+ name,
+ helpfmt,
+ helptxt,
+ execfmt,
+ toplevel,
+ kinds,
+ ns_only=False,
+ interactive=False,
+):
+ """Adds a CLI command to the CLI.
+
+ The command `cmd` is added to the commands executable by the user from the CLI.
+ See `run_command` above in the `doline` function and for the arguments that can
+ be passed in to this function.
+
+ Args:
+ unet: unet object
+ name: command string (no spaces)
+ helpfmt: format of command to display in help (left side)
+ helptxt: help string for command (right side)
+ execfmt: format string to insert user cmds into for execution. May also be a
+ dict of dict keyed on kind with sub-key of "exec" providing the `execfmt`
+ string for that kind.
+ toplevel: run command in common top-level namespaec not inside hosts
+ kinds: limit CLI command to nodes which match list of kinds.
+ ns_only: Should execute the command on the host vs in the node namespace.
+ interactive: Should execute the command inside an allocated pty (interactive)
+ """
+ unet.cli_run_cmds[name] = (
+ helpfmt,
+ helptxt,
+ execfmt,
+ toplevel,
+ kinds,
+ ns_only,
+ interactive,
+ )
+
+
+def add_cli_config(unet, config):
+ """Adds CLI commands based on config.
+
+ All exec strings will have %CONFIGDIR%, %NAME% and %RUNDIR% replaced with the
+ corresponding config directory and the current nodes `name` and `rundir`.
+ Additionally, the exec string will have f-string style interpolation performed
+ with the local variables `host` (node object or None), `unet` (Munet object) and
+ `user_input` (if provided to the CLI command) defined.
+
+ The format of the config dictionary can be seen in the following example.
+ The first list entry represents the default command because it has no `name` key.
+
+ commands:
+ - help: "run the given FRR command using vtysh"
+ format: "[HOST ...] FRR-CLI-COMMAND"
+ exec: "vtysh -c {}"
+ ns-only: false # the default
+ interactive: false # the default
+ - name: "vtysh"
+ help: "Open a FRR CLI inside new terminal[s] on the given HOST[s]"
+ format: "vtysh HOST [HOST ...]"
+ exec: "vtysh"
+ new-window: true
+ - name: "capture"
+ help: "Capture packets on a given network"
+ format: "pcap NETWORK"
+ exec: "tshark -s 9200 -i {0} -w /tmp/capture-{0}.pcap"
+ new-window: true
+ top-level: true # run in top-level container namespace, above hosts
+
+ The `new_window` key can also be a dictionary which will be passed as keyward
+ arguments to the `Commander.run_in_window()` function.
+
+ Args:
+ unet: unet object
+ config: dictionary of cli config
+ """
+ for cli_cmd in config.get("commands", []):
+ name = cli_cmd.get("name", None)
+ helpfmt = cli_cmd.get("format", "")
+ helptxt = cli_cmd.get("help", "")
+ execfmt = list_to_dict_with_key(cli_cmd.get("exec-kind"), "kind")
+ if not execfmt:
+ execfmt = cli_cmd.get("exec", "bash -c '{}'")
+ toplevel = cli_cmd.get("top-level", False)
+ kinds = cli_cmd.get("kinds", [])
+ stdargs = (unet, name, helpfmt, helptxt, execfmt, toplevel, kinds)
+ new_window = cli_cmd.get("new-window", None)
+ if isinstance(new_window, dict):
+ add_cli_in_window_cmd(*stdargs, **new_window)
+ elif bool(new_window):
+ add_cli_in_window_cmd(*stdargs)
+ else:
+ # on-host is deprecated it really implemented "ns-only"
+ add_cli_run_cmd(
+ *stdargs,
+ cli_cmd.get("ns-only", cli_cmd.get("on-host")),
+ cli_cmd.get("interactive", False),
+ )
+
+
+def cli(
+ unet,
+ histfile=None,
+ sockpath=None,
+ force_window=False,
+ title=None,
+ prompt=None,
+ background=True,
+):
+ asyncio.run(
+ async_cli(unet, histfile, sockpath, force_window, title, prompt, background)
+ )
+
+
+async def async_cli(
+ unet,
+ histfile=None,
+ sockpath=None,
+ force_window=False,
+ title=None,
+ prompt=None,
+ background=True,
+):
+ if prompt is None:
+ prompt = "munet> "
+
+ if force_window or not sys.stdin.isatty():
+ await remote_cli(unet, prompt, title, background)
+
+ if not unet:
+ logger.debug("client-cli using sockpath %s", sockpath)
+
+ try:
+ if sockpath:
+ await cli_client(sockpath, prompt)
+ else:
+ await local_cli(unet, sys.stdout, prompt, histfile, background)
+ except KeyboardInterrupt:
+ print("\n...^C exiting CLI")
+ except EOFError:
+ pass
+ except Exception as ex:
+ logger.critical("cli: got exception: %s", ex, exc_info=True)
+ raise
+
+
+if __name__ == "__main__":
+ # logging.basicConfig(level=logging.DEBUG, filename="/tmp/topotests/cli-client.log")
+ logging.basicConfig(level=logging.DEBUG)
+ logger = logging.getLogger("cli-client")
+ logger.info("Start logging cli-client")
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--histfile", help="file to user for history")
+ parser.add_argument("--prompt", help="prompt string to use")
+ parser.add_argument("socket", help="path to pair of sockets to communicate over")
+ cli_args = parser.parse_args()
+
+ cli_prompt = cli_args.prompt if cli_args.prompt else "munet> "
+ asyncio.run(
+ async_cli(
+ None,
+ cli_args.histfile,
+ cli_args.socket,
+ prompt=cli_prompt,
+ background=False,
+ )
+ )
diff --git a/tests/topotests/munet/compat.py b/tests/topotests/munet/compat.py
new file mode 100644
index 0000000000..e82a7d5b77
--- /dev/null
+++ b/tests/topotests/munet/compat.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# November 16 2022, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2022, LabN Consulting, L.L.C.
+#
+"""Provide compatible APIs."""
+
+
+class PytestConfig:
+ """Pytest config duck-type-compatible object using argprase args."""
+
+ class Namespace:
+ """A namespace defined by a dictionary of values."""
+
+ def __init__(self, args):
+ self.args = args
+
+ def __getattr__(self, attr):
+ return self.args[attr] if attr in self.args else None
+
+ def __init__(self, args):
+ self.args = vars(args)
+ self.option = PytestConfig.Namespace(self.args)
+
+ def getoption(self, name, default=None, skip=False):
+ assert not skip
+ if name.startswith("--"):
+ name = name[2:]
+ name = name.replace("-", "_")
+ if name in self.args:
+ return self.args[name] if self.args[name] is not None else default
+ return default
diff --git a/tests/topotests/munet/config.py b/tests/topotests/munet/config.py
new file mode 100644
index 0000000000..2870ae615c
--- /dev/null
+++ b/tests/topotests/munet/config.py
@@ -0,0 +1,213 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# June 25 2022, Christian Hopps <chopps@gmail.com>
+#
+# Copyright (c) 2021-2022, LabN Consulting, L.L.C.
+#
+"""A module that defines common configuration utility functions."""
+import logging
+
+from collections.abc import Iterable
+from copy import deepcopy
+from typing import overload
+
+
+def find_with_kv(lst, k, v):
+ if lst:
+ for e in lst:
+ if k in e and e[k] == v:
+ return e
+ return {}
+
+
+def find_all_with_kv(lst, k, v):
+ rv = []
+ if lst:
+ for e in lst:
+ if k in e and e[k] == v:
+ rv.append(e)
+ return rv
+
+
+def find_matching_net_config(name, cconf, oconf):
+ p = find_all_with_kv(oconf.get("connections", {}), "to", name)
+ if not p:
+ return {}
+
+ rname = cconf.get("remote-name", None)
+ if not rname:
+ return p[0]
+
+ return find_with_kv(p, "name", rname)
+
+
+def merge_using_key(a, b, k):
+ # First get a dict of indexes in `a` for the key value of `k` in objects of `a`
+ m = list(a)
+ mi = {o[k]: i for i, o in enumerate(m)}
+ for o in b:
+ bkv = o[k]
+ if bkv in mi:
+ m[mi[bkv]] = o
+ else:
+ mi[bkv] = len(m)
+ m.append(o)
+ return m
+
+
+def list_to_dict_with_key(lst, k):
+ """Convert a YANG styl list of objects to dict of objects.
+
+ This function converts a YANG style list of objects (dictionaries) to a plain python
+ dictionary of objects (dictionaries). The value for the supplied key for each
+ object is used to store the object in the new diciontary.
+
+ This only works for lists of objects which are keyed on a single contained value.
+
+ Args:
+ lst: a *list* of python dictionary objects.
+ k: the key value contained in each dictionary object in the list.
+
+ Returns:
+ A dictionary of objects (dictionaries).
+ """
+ return {x[k]: x for x in (lst if lst else [])}
+
+
+def config_to_dict_with_key(c, ck, k):
+ """Convert the config item from a list of objects to dict.
+
+ Use :py:func:`list_to_dict_with_key` to convert the list of objects
+ at ``c[ck]`` to a dict of the objects using the key ``k``.
+
+ Args:
+ c: config dictionary
+ ck: The key identifying the list of objects from ``c``.
+ k: The key to pass to :py:func:`list_to_dict_with_key`.
+
+ Returns:
+ A dictionary of objects (dictionaries).
+ """
+ c[ck] = list_to_dict_with_key(c.get(ck, []), k)
+ return c[ck]
+
+
+@overload
+def config_subst(config: str, **kwargs) -> str:
+ ...
+
+
+@overload
+def config_subst(config: Iterable, **kwargs) -> Iterable:
+ ...
+
+
+def config_subst(config: Iterable, **kwargs) -> Iterable:
+ if isinstance(config, str):
+ if "%RUNDIR%/%NAME%" in config:
+ config = config.replace("%RUNDIR%/%NAME%", "%RUNDIR%")
+ logging.warning(
+ "config '%RUNDIR%/%NAME%' should be changed to '%RUNDIR%' only, "
+ "converting automatically for now."
+ )
+ for name, value in kwargs.items():
+ config = config.replace(f"%{name.upper()}%", str(value))
+ elif isinstance(config, Iterable):
+ try:
+ return {k: config_subst(config[k], **kwargs) for k in config}
+ except (KeyError, TypeError):
+ return [config_subst(x, **kwargs) for x in config]
+ return config
+
+
+def value_merge_deepcopy(s1, s2):
+ """Merge values using deepcopy.
+
+ Create a deepcopy of the result of merging the values from dicts ``s1`` and ``s2``.
+ If a key exists in both ``s1`` and ``s2`` the value from ``s2`` is used."
+ """
+ d = {}
+ for k, v in s1.items():
+ if k in s2:
+ d[k] = deepcopy(s2[k])
+ else:
+ d[k] = deepcopy(v)
+ return d
+
+
+def merge_kind_config(kconf, config):
+ mergekeys = kconf.get("merge", [])
+ config = deepcopy(config)
+ new = deepcopy(kconf)
+ for k in new:
+ if k not in config:
+ continue
+
+ if k not in mergekeys:
+ new[k] = config[k]
+ elif isinstance(new[k], list):
+ new[k].extend(config[k])
+ elif isinstance(new[k], dict):
+ new[k] = {**new[k], **config[k]}
+ else:
+ new[k] = config[k]
+ for k in config:
+ if k not in new:
+ new[k] = config[k]
+ return new
+
+
+def cli_opt_list(option_list):
+ if not option_list:
+ return []
+ if isinstance(option_list, str):
+ return [x for x in option_list.split(",") if x]
+ return [x for x in option_list if x]
+
+
+def name_in_cli_opt_str(name, option_list):
+ ol = cli_opt_list(option_list)
+ return name in ol or "all" in ol
+
+
+class ConfigOptionsProxy:
+ """Proxy options object to fill in for any missing pytest config."""
+
+ class DefNoneObject:
+ """An object that returns None for any attribute access."""
+
+ def __getattr__(self, attr):
+ return None
+
+ def __init__(self, pytestconfig=None):
+ if isinstance(pytestconfig, ConfigOptionsProxy):
+ self.config = pytestconfig.config
+ self.option = self.config.option
+ else:
+ self.config = pytestconfig
+ if self.config:
+ self.option = self.config.option
+ else:
+ self.option = ConfigOptionsProxy.DefNoneObject()
+
+ def getoption(self, opt, default=None):
+ if not self.config:
+ return default
+
+ try:
+ value = self.config.getoption(opt)
+ return value if value is not None else default
+ except ValueError:
+ return default
+
+ def get_option(self, opt, default=None):
+ return self.getoption(opt, default)
+
+ def get_option_list(self, opt):
+ value = self.get_option(opt, "")
+ return cli_opt_list(value)
+
+ def name_in_option_list(self, name, opt):
+ optlist = self.get_option_list(opt)
+ return "all" in optlist or name in optlist
diff --git a/tests/topotests/munet/kinds.yaml b/tests/topotests/munet/kinds.yaml
new file mode 100644
index 0000000000..0c278d37c9
--- /dev/null
+++ b/tests/topotests/munet/kinds.yaml
@@ -0,0 +1,84 @@
+version: 1
+kinds:
+ - name: frr
+ cap-add:
+ # Zebra requires these
+ - NET_ADMIN
+ - NET_RAW
+ - SYS_ADMIN
+ - AUDIT_WRITE # needed for ssh pty allocation
+ - name: ceos
+ init: false
+ shell: false
+ merge: ["env"]
+ # Should we cap-drop some of these in privileged mode?
+ # ceos kind is special. munet will add args to /sbin/init for each
+ # environment variable of the form `systemd.setenv=ENVNAME=VALUE` for each
+ # environment varialbe named ENVNAME with a value of `VALUE`. If cmd: is
+ # changed to anything but `/sbin/init` munet will not do this.
+ cmd: /sbin/init
+ privileged: true
+ env:
+ - name: "EOS_PLATFORM"
+ value: "ceoslab"
+ - name: "container"
+ value: "docker"
+ - name: "ETBA"
+ value: "4"
+ - name: "SKIP_ZEROTOUCH_BARRIER_IN_SYSDBINIT"
+ value: "1"
+ - name: "INTFTYPE"
+ value: "eth"
+ - name: "MAPETH0"
+ value: "1"
+ - name: "MGMT_INTF"
+ value: "eth0"
+ - name: "CEOS"
+ value: "1"
+
+ # cap-add:
+ # # cEOS requires these, except GNMI still doesn't work
+ # # - NET_ADMIN
+ # # - NET_RAW
+ # # - SYS_ADMIN
+ # # - SYS_RESOURCE # Required for the CLI
+
+ # All Caps
+ # - AUDIT_CONTROL
+ # - AUDIT_READ
+ # - AUDIT_WRITE
+ # - BLOCK_SUSPEND
+ # - CHOWN
+ # - DAC_OVERRIDE
+ # - DAC_READ_SEARCH
+ # - FOWNER
+ # - FSETID
+ # - IPC_LOCK
+ # - IPC_OWNER
+ # - KILL
+ # - LEASE
+ # - LINUX_IMMUTABLE
+ # - MKNOD
+ # - NET_ADMIN
+ # - NET_BIND_SERVICE
+ # - NET_BROADCAST
+ # - NET_RAW
+ # - SETFCAP
+ # - SETGID
+ # - SETPCAP
+ # - SETUID
+ # - SYSLOG
+ # - SYS_ADMIN
+ # - SYS_BOOT
+ # - SYS_CHROOT
+ # - SYS_MODULE
+ # - SYS_NICE
+ # - SYS_PACCT
+ # - SYS_PTRACE
+ # - SYS_RAWIO
+ # - SYS_RESOURCE
+ # - SYS_TIME
+ # - SYS_TTY_CONFIG
+ # - WAKE_ALARM
+ # - MAC_ADMIN - Smack project?
+ # - MAC_OVERRIDE - Smack project?
diff --git a/tests/topotests/munet/linux.py b/tests/topotests/munet/linux.py
new file mode 100644
index 0000000000..417f74566a
--- /dev/null
+++ b/tests/topotests/munet/linux.py
@@ -0,0 +1,267 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# June 10 2022, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2022, LabN Consulting, L.L.C.
+#
+"""A module that gives access to linux unshare system call."""
+
+import ctypes # pylint: disable=C0415
+import ctypes.util # pylint: disable=C0415
+import errno
+import functools
+import os
+
+
+libc = None
+
+
+def raise_oserror(enum):
+ s = errno.errorcode[enum] if enum in errno.errorcode else str(enum)
+ error = OSError(s)
+ error.errno = enum
+ error.strerror = s
+ raise error
+
+
+def _load_libc():
+ global libc # pylint: disable=W0601,W0603
+ if libc:
+ return
+ lcpath = ctypes.util.find_library("c")
+ libc = ctypes.CDLL(lcpath, use_errno=True)
+
+
+def pause():
+ if not libc:
+ _load_libc()
+ libc.pause()
+
+
+MS_RDONLY = 1
+MS_NOSUID = 1 << 1
+MS_NODEV = 1 << 2
+MS_NOEXEC = 1 << 3
+MS_SYNCHRONOUS = 1 << 4
+MS_REMOUNT = 1 << 5
+MS_MANDLOCK = 1 << 6
+MS_DIRSYNC = 1 << 7
+MS_NOSYMFOLLOW = 1 << 8
+MS_NOATIME = 1 << 10
+MS_NODIRATIME = 1 << 11
+MS_BIND = 1 << 12
+MS_MOVE = 1 << 13
+MS_REC = 1 << 14
+MS_SILENT = 1 << 15
+MS_POSIXACL = 1 << 16
+MS_UNBINDABLE = 1 << 17
+MS_PRIVATE = 1 << 18
+MS_SLAVE = 1 << 19
+MS_SHARED = 1 << 20
+MS_RELATIME = 1 << 21
+MS_KERNMOUNT = 1 << 22
+MS_I_VERSION = 1 << 23
+MS_STRICTATIME = 1 << 24
+MS_LAZYTIME = 1 << 25
+
+
+def mount(source, target, fs, flags=0, options=""):
+ if not libc:
+ _load_libc()
+ libc.mount.argtypes = (
+ ctypes.c_char_p,
+ ctypes.c_char_p,
+ ctypes.c_char_p,
+ ctypes.c_ulong,
+ ctypes.c_char_p,
+ )
+ fsenc = fs.encode() if fs else None
+ optenc = options.encode() if options else None
+ ret = libc.mount(source.encode(), target.encode(), fsenc, flags, optenc)
+ if ret < 0:
+ err = ctypes.get_errno()
+ raise OSError(
+ err,
+ f"Error mounting {source} ({fs}) on {target}"
+ f" with options '{options}': {os.strerror(err)}",
+ )
+
+
+# unmout options
+MNT_FORCE = 0x1
+MNT_DETACH = 0x2
+MNT_EXPIRE = 0x4
+UMOUNT_NOFOLLOW = 0x8
+
+
+def umount(target, options):
+ if not libc:
+ _load_libc()
+ libc.umount.argtypes = (ctypes.c_char_p, ctypes.c_uint)
+
+ ret = libc.umount(target.encode(), int(options))
+ if ret < 0:
+ err = ctypes.get_errno()
+ raise OSError(
+ err,
+ f"Error umounting {target} with options '{options}': {os.strerror(err)}",
+ )
+
+
+def pidfd_open(pid, flags=0):
+ if hasattr(os, "pidfd_open") and os.pidfd_open is not pidfd_open:
+ return os.pidfd_open(pid, flags) # pylint: disable=no-member
+
+ if not libc:
+ _load_libc()
+
+ try:
+ pfof = libc.pidfd_open
+ except AttributeError:
+ __NR_pidfd_open = 434
+ _pidfd_open = libc.syscall
+ _pidfd_open.restype = ctypes.c_int
+ _pidfd_open.argtypes = ctypes.c_long, ctypes.c_uint, ctypes.c_uint
+ pfof = functools.partial(_pidfd_open, __NR_pidfd_open)
+
+ fd = pfof(int(pid), int(flags))
+ if fd == -1:
+ raise_oserror(ctypes.get_errno())
+
+ return fd
+
+
+if not hasattr(os, "pidfd_open"):
+ os.pidfd_open = pidfd_open
+
+
+def setns(fd, nstype): # noqa: D402
+ """See setns(2) manpage."""
+ if not libc:
+ _load_libc()
+
+ if libc.setns(int(fd), int(nstype)) == -1:
+ raise_oserror(ctypes.get_errno())
+
+
+def unshare(flags): # noqa: D402
+ """See unshare(2) manpage."""
+ if not libc:
+ _load_libc()
+
+ if libc.unshare(int(flags)) == -1:
+ raise_oserror(ctypes.get_errno())
+
+
+CLONE_NEWTIME = 0x00000080
+CLONE_VM = 0x00000100
+CLONE_FS = 0x00000200
+CLONE_FILES = 0x00000400
+CLONE_SIGHAND = 0x00000800
+CLONE_PIDFD = 0x00001000
+CLONE_PTRACE = 0x00002000
+CLONE_VFORK = 0x00004000
+CLONE_PARENT = 0x00008000
+CLONE_THREAD = 0x00010000
+CLONE_NEWNS = 0x00020000
+CLONE_SYSVSEM = 0x00040000
+CLONE_SETTLS = 0x00080000
+CLONE_PARENT_SETTID = 0x00100000
+CLONE_CHILD_CLEARTID = 0x00200000
+CLONE_DETACHED = 0x00400000
+CLONE_UNTRACED = 0x00800000
+CLONE_CHILD_SETTID = 0x01000000
+CLONE_NEWCGROUP = 0x02000000
+CLONE_NEWUTS = 0x04000000
+CLONE_NEWIPC = 0x08000000
+CLONE_NEWUSER = 0x10000000
+CLONE_NEWPID = 0x20000000
+CLONE_NEWNET = 0x40000000
+CLONE_IO = 0x80000000
+
+clone_flag_names = {
+ CLONE_NEWTIME: "CLONE_NEWTIME",
+ CLONE_VM: "CLONE_VM",
+ CLONE_FS: "CLONE_FS",
+ CLONE_FILES: "CLONE_FILES",
+ CLONE_SIGHAND: "CLONE_SIGHAND",
+ CLONE_PIDFD: "CLONE_PIDFD",
+ CLONE_PTRACE: "CLONE_PTRACE",
+ CLONE_VFORK: "CLONE_VFORK",
+ CLONE_PARENT: "CLONE_PARENT",
+ CLONE_THREAD: "CLONE_THREAD",
+ CLONE_NEWNS: "CLONE_NEWNS",
+ CLONE_SYSVSEM: "CLONE_SYSVSEM",
+ CLONE_SETTLS: "CLONE_SETTLS",
+ CLONE_PARENT_SETTID: "CLONE_PARENT_SETTID",
+ CLONE_CHILD_CLEARTID: "CLONE_CHILD_CLEARTID",
+ CLONE_DETACHED: "CLONE_DETACHED",
+ CLONE_UNTRACED: "CLONE_UNTRACED",
+ CLONE_CHILD_SETTID: "CLONE_CHILD_SETTID",
+ CLONE_NEWCGROUP: "CLONE_NEWCGROUP",
+ CLONE_NEWUTS: "CLONE_NEWUTS",
+ CLONE_NEWIPC: "CLONE_NEWIPC",
+ CLONE_NEWUSER: "CLONE_NEWUSER",
+ CLONE_NEWPID: "CLONE_NEWPID",
+ CLONE_NEWNET: "CLONE_NEWNET",
+ CLONE_IO: "CLONE_IO",
+}
+
+
+def clone_flag_string(flags):
+ ns = [v for k, v in clone_flag_names.items() if k & flags]
+ if ns:
+ return "|".join(ns)
+ return "None"
+
+
+namespace_files = {
+ CLONE_NEWUSER: "ns/user",
+ CLONE_NEWCGROUP: "ns/cgroup",
+ CLONE_NEWIPC: "ns/ipc",
+ CLONE_NEWUTS: "ns/uts",
+ CLONE_NEWNET: "ns/net",
+ CLONE_NEWPID: "ns/pid_for_children",
+ CLONE_NEWNS: "ns/mnt",
+ CLONE_NEWTIME: "ns/time_for_children",
+}
+
+PR_SET_PDEATHSIG = 1
+PR_GET_PDEATHSIG = 2
+PR_SET_NAME = 15
+PR_GET_NAME = 16
+
+
+def set_process_name(name):
+ if not libc:
+ _load_libc()
+
+ # Why does uncommenting this cause failure?
+ # libc.prctl.argtypes = (
+ # ctypes.c_int,
+ # ctypes.c_ulong,
+ # ctypes.c_ulong,
+ # ctypes.c_ulong,
+ # ctypes.c_ulong,
+ # )
+
+ s = ctypes.create_string_buffer(bytes(name, encoding="ascii"))
+ sr = ctypes.byref(s)
+ libc.prctl(PR_SET_NAME, sr, 0, 0, 0)
+
+
+def set_parent_death_signal(signum):
+ if not libc:
+ _load_libc()
+
+ # Why does uncommenting this cause failure?
+ libc.prctl.argtypes = (
+ ctypes.c_int,
+ ctypes.c_ulong,
+ ctypes.c_ulong,
+ ctypes.c_ulong,
+ ctypes.c_ulong,
+ )
+
+ libc.prctl(PR_SET_PDEATHSIG, signum, 0, 0, 0)
diff --git a/tests/topotests/munet/logconf-mutest.yaml b/tests/topotests/munet/logconf-mutest.yaml
new file mode 100644
index 0000000000..b450fb9382
--- /dev/null
+++ b/tests/topotests/munet/logconf-mutest.yaml
@@ -0,0 +1,84 @@
+version: 1
+formatters:
+ brief:
+ format: '%(levelname)5s: %(message)s'
+ operfmt:
+ class: munet.mulog.ColorFormatter
+ format: ' ------| %(message)s'
+ exec:
+ format: '%(asctime)s %(levelname)5s: %(name)s: %(message)s'
+ output:
+ format: '%(asctime)s %(levelname)5s: OUTPUT: %(message)s'
+ results:
+ # format: '%(asctime)s %(levelname)5s: %(message)s'
+ format: '%(message)s'
+
+handlers:
+ console:
+ level: WARNING
+ class: logging.StreamHandler
+ formatter: brief
+ stream: ext://sys.stderr
+ info_console:
+ level: INFO
+ class: logging.StreamHandler
+ formatter: brief
+ stream: ext://sys.stderr
+ oper_console:
+ level: DEBUG
+ class: logging.StreamHandler
+ formatter: operfmt
+ stream: ext://sys.stderr
+ exec:
+ level: DEBUG
+ class: logging.FileHandler
+ formatter: exec
+ filename: mutest-exec.log
+ mode: w
+ output:
+ level: DEBUG
+ class: munet.mulog.MultiFileHandler
+ root_path: "mutest.output"
+ formatter: output
+ filename: mutest-output.log
+ mode: w
+ results:
+ level: INFO
+ class: munet.mulog.MultiFileHandler
+ root_path: "mutest.results"
+ new_handler_level: DEBUG
+ formatter: results
+ filename: mutest-results.log
+ mode: w
+
+root:
+ level: DEBUG
+ handlers: [ "console", "exec" ]
+
+loggers:
+ # These are some loggers that get used...
+ # munet:
+ # level: DEBUG
+ # propagate: true
+ # munet.base.commander
+ # level: DEBUG
+ # propagate: true
+ # mutest.error:
+ # level: DEBUG
+ # propagate: true
+ mutest.output:
+ level: DEBUG
+ handlers: ["output", "exec"]
+ propagate: false
+ mutest.results:
+ level: DEBUG
+ handlers: [ "info_console", "exec", "output", "results" ]
+ # We don't propagate this b/c we want a lower level accept on the console
+ # Instead we use info_console and exec to cover what root would log to.
+ propagate: false
+ # This is used to debug the operation of mutest
+ mutest.oper:
+ # Records are emitted at DEBUG so this will normally filter everything
+ level: INFO
+ handlers: [ "oper_console" ]
+ propagate: false
diff --git a/tests/topotests/munet/logconf.yaml b/tests/topotests/munet/logconf.yaml
new file mode 100644
index 0000000000..430ee2096d
--- /dev/null
+++ b/tests/topotests/munet/logconf.yaml
@@ -0,0 +1,32 @@
+version: 1
+formatters:
+ brief:
+ format: '%(asctime)s: %(levelname)s: %(message)s'
+ precise:
+ format: '%(asctime)s %(levelname)s: %(name)s: %(message)s'
+
+handlers:
+ console:
+ class: logging.StreamHandler
+ formatter: brief
+ level: INFO
+ stream: ext://sys.stderr
+ file:
+ class: logging.FileHandler
+ formatter: precise
+ level: DEBUG
+ filename: munet-exec.log
+ mode: w
+
+root:
+ level: DEBUG
+ handlers: [ "console", "file" ]
+
+# these are some loggers that get used.
+# loggers:
+# munet:
+# level: DEBUG
+# propagate: true
+# munet.base.commander
+# level: DEBUG
+# propagate: true
diff --git a/tests/topotests/munet/mucmd.py b/tests/topotests/munet/mucmd.py
new file mode 100644
index 0000000000..5518c6dcfe
--- /dev/null
+++ b/tests/topotests/munet/mucmd.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# December 5 2021, Christian Hopps <chopps@labn.net>
+#
+# Copyright 2021, LabN Consulting, L.L.C.
+#
+"""A command that allows external command execution inside nodes."""
+import argparse
+import json
+import os
+import subprocess
+import sys
+
+from pathlib import Path
+
+
+def newest_file_in(filename, paths, has_sibling=None):
+ new = None
+ newst = None
+ items = (x for y in paths for x in Path(y).rglob(filename))
+ for e in items:
+ st = os.stat(e)
+ if has_sibling and not e.parent.joinpath(has_sibling).exists():
+ continue
+ if not new or st.st_mtime_ns > newst.st_mtime_ns:
+ new = e
+ newst = st
+ continue
+ return new, newst
+
+
+def main(*args):
+ ap = argparse.ArgumentParser(args)
+ ap.add_argument("-d", "--rundir", help="runtime directory for tempfiles, logs, etc")
+ ap.add_argument("node", nargs="?", help="node to enter or run command inside")
+ ap.add_argument(
+ "shellcmd",
+ nargs=argparse.REMAINDER,
+ help="optional shell-command to execute on NODE",
+ )
+ args = ap.parse_args()
+ if args.rundir:
+ configpath = Path(args.rundir).joinpath("config.json")
+ else:
+ configpath, _ = newest_file_in(
+ "config.json",
+ ["/tmp/munet", "/tmp/mutest", "/tmp/unet-test"],
+ has_sibling=args.node,
+ )
+ print(f'Using "{configpath}"')
+
+ if not configpath.exists():
+ print(f'"{configpath}" not found')
+ return 1
+ rundir = configpath.parent
+
+ nodes = []
+ config = json.load(open(configpath, encoding="utf-8"))
+ nodes = list(config.get("topology", {}).get("nodes", []))
+ envcfg = config.get("mucmd", {}).get("env", {})
+
+ # If args.node is not a node it's part of shellcmd
+ if args.node and args.node not in nodes:
+ if args.node != ".":
+ args.shellcmd[0:0] = [args.node]
+ args.node = None
+
+ if args.node:
+ name = args.node
+ nodedir = rundir.joinpath(name)
+ if not nodedir.exists():
+ print('"{name}" node doesn\'t exist in "{rundir}"')
+ return 1
+ rundir = nodedir
+ else:
+ name = "munet"
+ pidpath = rundir.joinpath("nspid")
+ pid = open(pidpath, encoding="ascii").read().strip()
+
+ env = {**os.environ}
+ env["MUNET_NODENAME"] = name
+ env["MUNET_RUNDIR"] = str(rundir)
+
+ for k in envcfg:
+ envcfg[k] = envcfg[k].replace("%NAME%", str(name))
+ envcfg[k] = envcfg[k].replace("%RUNDIR%", str(rundir))
+
+ # Can't use -F if it's a new pid namespace
+ ecmd = "/usr/bin/nsenter"
+ eargs = [ecmd]
+
+ output = subprocess.check_output(["/usr/bin/nsenter", "--help"], encoding="utf-8")
+ if " -a," in output:
+ eargs.append("-a")
+ else:
+ # -U doesn't work
+ for flag in ["-u", "-i", "-m", "-n", "-C", "-T"]:
+ if f" {flag}," in output:
+ eargs.append(flag)
+ eargs.append(f"--pid=/proc/{pid}/ns/pid_for_children")
+ eargs.append(f"--wd={rundir}")
+ eargs.extend(["-t", pid])
+ eargs += args.shellcmd
+ # print("Using ", eargs)
+ return os.execvpe(ecmd, eargs, {**env, **envcfg})
+
+
+if __name__ == "__main__":
+ exit_status = main()
+ sys.exit(exit_status)
diff --git a/tests/topotests/munet/mulog.py b/tests/topotests/munet/mulog.py
new file mode 100644
index 0000000000..f840eae2d8
--- /dev/null
+++ b/tests/topotests/munet/mulog.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# December 4 2022, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2022, LabN Consulting, L.L.C.
+#
+"""Utilities for logging in munet."""
+
+import logging
+
+from pathlib import Path
+
+
+class MultiFileHandler(logging.FileHandler):
+ """A logging handler that logs to new files based on the logger name.
+
+ The MultiFileHandler operates as a FileHandler with additional functionality. In
+ addition to logging to the specified logging file MultiFileHandler also creates new
+ FileHandlers for child loggers based on a root logging name path.
+
+ The ``root_path`` determines when to create a new FileHandler. For each received log
+ record, ``root_path`` is removed from the logger name of the record if present, and
+ the resulting channel path (if any) determines the directory for a new log file to
+ also emit the record to. The new file path is constructed by starting with the
+ directory ``filename`` resides in, then joining the path determined above after
+ converting "." to "/" and finally by adding back the basename of ``filename``.
+
+ record logger path => mutest.output.testingfoo
+ root_path => mutest.output
+ base filename => /tmp/mutest/mutest-exec.log
+ new logfile => /tmp/mutest/testingfoo/mutest-exec.log
+
+ All messages are also emitted to the common FileLogger for ``filename``.
+
+ If a log record is from a logger that does not start with ``root_path`` no file is
+ created and the normal emit occurs.
+
+ Args:
+ root_path: the logging path of the root level for this handler.
+ new_handler_level: logging level for newly created handlers
+ log_dir: the log directory to put log files in.
+ filename: the base log file.
+ """
+
+ def __init__(self, root_path, filename=None, **kwargs):
+ self.__root_path = root_path
+ self.__basename = Path(filename).name
+ if root_path[-1] != ".":
+ self.__root_path += "."
+ self.__root_pathlen = len(self.__root_path)
+ self.__kwargs = kwargs
+ self.__log_dir = Path(filename).absolute().parent
+ self.__log_dir.mkdir(parents=True, exist_ok=True)
+ self.__filenames = {}
+ self.__added = set()
+
+ if "new_handler_level" not in kwargs:
+ self.__new_handler_level = logging.NOTSET
+ else:
+ new_handler_level = kwargs["new_handler_level"]
+ del kwargs["new_handler_level"]
+ self.__new_handler_level = new_handler_level
+
+ super().__init__(filename=filename, **kwargs)
+
+ if self.__new_handler_level is None:
+ self.__new_handler_level = self.level
+
+ def __log_filename(self, name):
+ if name in self.__filenames:
+ return self.__filenames[name]
+
+ if not name.startswith(self.__root_path):
+ newname = None
+ else:
+ newname = name[self.__root_pathlen :]
+ newname = Path(newname.replace(".", "/"))
+ newname = self.__log_dir.joinpath(newname)
+ newname = newname.joinpath(self.__basename)
+ self.__filenames[name] = newname
+
+ self.__filenames[name] = newname
+ return newname
+
+ def emit(self, record):
+ newname = self.__log_filename(record.name)
+ if newname:
+ if newname not in self.__added:
+ self.__added.add(newname)
+ h = logging.FileHandler(filename=newname, **self.__kwargs)
+ h.setLevel(self.__new_handler_level)
+ h.setFormatter(self.formatter)
+ logging.getLogger(record.name).addHandler(h)
+ h.emit(record)
+ super().emit(record)
+
+
+class ColorFormatter(logging.Formatter):
+ """A formatter that adds color sequences based on level."""
+
+ def __init__(self, fmt=None, datefmt=None, style="%", **kwargs):
+ grey = "\x1b[90m"
+ yellow = "\x1b[33m"
+ red = "\x1b[31m"
+ bold_red = "\x1b[31;1m"
+ reset = "\x1b[0m"
+ # basefmt = " ------| %(message)s "
+
+ self.formatters = {
+ logging.DEBUG: logging.Formatter(grey + fmt + reset),
+ logging.INFO: logging.Formatter(grey + fmt + reset),
+ logging.WARNING: logging.Formatter(yellow + fmt + reset),
+ logging.ERROR: logging.Formatter(red + fmt + reset),
+ logging.CRITICAL: logging.Formatter(bold_red + fmt + reset),
+ }
+ # Why are we even bothering?
+ super().__init__(fmt, datefmt, style, **kwargs)
+
+ def format(self, record):
+ formatter = self.formatters.get(record.levelno)
+ return formatter.format(record)
diff --git a/tests/topotests/munet/munet-schema.json b/tests/topotests/munet/munet-schema.json
new file mode 100644
index 0000000000..a1dcd878dd
--- /dev/null
+++ b/tests/topotests/munet/munet-schema.json
@@ -0,0 +1,654 @@
+{
+ "title": "labn-munet-config",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "description": "Generated by pyang from module labn-munet-config",
+ "type": "object",
+ "properties": {
+ "cli": {
+ "type": "object",
+ "properties": {
+ "commands": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "exec": {
+ "type": "string"
+ },
+ "exec-kind": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "kind": {
+ "type": "string"
+ },
+ "exec": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "format": {
+ "type": "string"
+ },
+ "help": {
+ "type": "string"
+ },
+ "interactive": {
+ "type": "boolean"
+ },
+ "kinds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "name": {
+ "type": "string"
+ },
+ "new-window": {
+ "type": "boolean"
+ },
+ "top-level": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+ },
+ "kinds": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "merge": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "cap-add": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "cap-remove": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "cmd": {
+ "type": "string"
+ },
+ "cleanup-cmd": {
+ "type": "string"
+ },
+ "ready-cmd": {
+ "type": "string"
+ },
+ "image": {
+ "type": "string"
+ },
+ "server": {
+ "type": "string"
+ },
+ "server-port": {
+ "type": "number"
+ },
+ "qemu": {
+ "type": "object",
+ "properties": {
+ "bios": {
+ "type": "string"
+ },
+ "disk": {
+ "type": "string"
+ },
+ "kerenel": {
+ "type": "string"
+ },
+ "initrd": {
+ "type": "string"
+ },
+ "kvm": {
+ "type": "boolean"
+ },
+ "ncpu": {
+ "type": "integer"
+ },
+ "memory": {
+ "type": "string"
+ },
+ "root": {
+ "type": "string"
+ },
+ "cmdline-extra": {
+ "type": "string"
+ },
+ "extra-args": {
+ "type": "string"
+ },
+ "console": {
+ "type": "object",
+ "properties": {
+ "user": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ },
+ "expects": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "sends": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "timeout": {
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "connections": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "to": {
+ "type": "string"
+ },
+ "ip": {
+ "type": "string"
+ },
+ "ipv6": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "hostintf": {
+ "type": "string"
+ },
+ "physical": {
+ "type": "string"
+ },
+ "remote-name": {
+ "type": "string"
+ },
+ "driver": {
+ "type": "string"
+ },
+ "delay": {
+ "type": "integer"
+ },
+ "jitter": {
+ "type": "integer"
+ },
+ "jitter-correlation": {
+ "type": "string"
+ },
+ "loss": {
+ "type": "integer"
+ },
+ "loss-correlation": {
+ "type": "string"
+ },
+ "rate": {
+ "type": "object",
+ "properties": {
+ "rate": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "limit": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "burst": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "env": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "gdb-cmd": {
+ "type": "string"
+ },
+ "gdb-target-cmds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "gdb-run-cmds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "init": {
+ "oneOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "mounts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "destination": {
+ "type": "string"
+ },
+ "source": {
+ "type": "string"
+ },
+ "tmpfs-size": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "name": {
+ "type": "string"
+ },
+ "podman": {
+ "type": "object",
+ "properties": {
+ "extra-args": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "privileged": {
+ "type": "boolean"
+ },
+ "shell": {
+ "oneOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "volumes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "topology": {
+ "type": "object",
+ "properties": {
+ "dns-network": {
+ "type": "string"
+ },
+ "ipv6-enable": {
+ "type": "boolean"
+ },
+ "networks-autonumber": {
+ "type": "boolean"
+ },
+ "networks": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "ip": {
+ "type": "string"
+ },
+ "ipv6": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "nodes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer"
+ },
+ "kind": {
+ "type": "string"
+ },
+ "cap-add": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "cap-remove": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "cmd": {
+ "type": "string"
+ },
+ "cleanup-cmd": {
+ "type": "string"
+ },
+ "ready-cmd": {
+ "type": "string"
+ },
+ "image": {
+ "type": "string"
+ },
+ "server": {
+ "type": "string"
+ },
+ "server-port": {
+ "type": "number"
+ },
+ "qemu": {
+ "type": "object",
+ "properties": {
+ "bios": {
+ "type": "string"
+ },
+ "disk": {
+ "type": "string"
+ },
+ "kerenel": {
+ "type": "string"
+ },
+ "initrd": {
+ "type": "string"
+ },
+ "kvm": {
+ "type": "boolean"
+ },
+ "ncpu": {
+ "type": "integer"
+ },
+ "memory": {
+ "type": "string"
+ },
+ "root": {
+ "type": "string"
+ },
+ "cmdline-extra": {
+ "type": "string"
+ },
+ "extra-args": {
+ "type": "string"
+ },
+ "console": {
+ "type": "object",
+ "properties": {
+ "user": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ },
+ "expects": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "sends": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "timeout": {
+ "type": "integer"
+ }
+ }
+ }
+ }
+ },
+ "connections": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "to": {
+ "type": "string"
+ },
+ "ip": {
+ "type": "string"
+ },
+ "ipv6": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "hostintf": {
+ "type": "string"
+ },
+ "physical": {
+ "type": "string"
+ },
+ "remote-name": {
+ "type": "string"
+ },
+ "driver": {
+ "type": "string"
+ },
+ "delay": {
+ "type": "integer"
+ },
+ "jitter": {
+ "type": "integer"
+ },
+ "jitter-correlation": {
+ "type": "string"
+ },
+ "loss": {
+ "type": "integer"
+ },
+ "loss-correlation": {
+ "type": "string"
+ },
+ "rate": {
+ "type": "object",
+ "properties": {
+ "rate": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "limit": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "burst": {
+ "oneOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "env": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "gdb-cmd": {
+ "type": "string"
+ },
+ "gdb-target-cmds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "gdb-run-cmds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "init": {
+ "oneOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "mounts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "destination": {
+ "type": "string"
+ },
+ "source": {
+ "type": "string"
+ },
+ "tmpfs-size": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "name": {
+ "type": "string"
+ },
+ "podman": {
+ "type": "object",
+ "properties": {
+ "extra-args": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "privileged": {
+ "type": "boolean"
+ },
+ "shell": {
+ "oneOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "volumes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "version": {
+ "type": "integer"
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/topotests/munet/mutest/__main__.py b/tests/topotests/munet/mutest/__main__.py
new file mode 100644
index 0000000000..c87031112d
--- /dev/null
+++ b/tests/topotests/munet/mutest/__main__.py
@@ -0,0 +1,445 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# December 2 2022, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2022, LabN Consulting, L.L.C.
+#
+"""Command to execute mutests."""
+
+import asyncio
+import logging
+import os
+import subprocess
+import sys
+import time
+
+from argparse import ArgumentParser
+from argparse import Namespace
+from copy import deepcopy
+from pathlib import Path
+from typing import Union
+
+from munet import parser
+from munet.base import Bridge
+from munet.base import get_event_loop
+from munet.mutest import userapi as uapi
+from munet.native import L3NodeMixin
+from munet.native import Munet
+from munet.parser import async_build_topology
+from munet.parser import get_config
+
+
+# We want all but critical to fit in 5 characters for alignment
+logging.addLevelName(logging.WARNING, "WARN")
+root_logger = logging.getLogger("")
+exec_formatter = logging.Formatter("%(asctime)s %(levelname)5s: %(name)s: %(message)s")
+
+
+async def get_unet(config: dict, croot: Path, rundir: Path, unshare: bool = False):
+ """Create and run a new Munet topology.
+
+ The topology is built from the given ``config`` to run inside the path indicated
+ by ``rundir``. If ``unshare`` is True then the process will unshare into it's
+ own private namespace.
+
+ Args:
+ config: a config dictionary obtained from ``munet.parser.get_config``. This
+ value will be modified and stored in the built ``Munet`` object.
+ croot: common root of all tests, used to search for ``kinds.yaml`` files.
+ rundir: the path to the run directory for this topology.
+ unshare: True to unshare the process into it's own private namespace.
+
+ Yields:
+ Munet: The constructed and running topology.
+ """
+ tasks = []
+ unet = None
+ try:
+ try:
+ unet = await async_build_topology(
+ config, rundir=str(rundir), unshare_inline=unshare
+ )
+ except Exception as error:
+ logging.debug("unet build failed: %s", error, exc_info=True)
+ raise
+ try:
+ tasks = await unet.run()
+ except Exception as error:
+ logging.debug("unet run failed: %s", error, exc_info=True)
+ raise
+ logging.debug("unet topology running")
+ try:
+ yield unet
+ except Exception as error:
+ logging.error("unet fixture: yield unet unexpected exception: %s", error)
+ raise
+ except KeyboardInterrupt:
+ logging.info("Received keyboard while building topology")
+ raise
+ finally:
+ if unet:
+ await unet.async_delete()
+
+ # No one ever awaits these so cancel them
+ logging.debug("unet fixture: cleanup")
+ for task in tasks:
+ task.cancel()
+
+ # Reset the class variables so auto number is predictable
+ logging.debug("unet fixture: resetting ords to 1")
+ L3NodeMixin.next_ord = 1
+ Bridge.next_ord = 1
+
+
+def common_root(path1: Union[str, Path], path2: Union[str, Path]) -> Path:
+ """Find the common root between 2 paths.
+
+ Args:
+ path1: Path
+ path2: Path
+ Returns:
+ Path: the shared root components between ``path1`` and ``path2``.
+
+ Examples:
+ >>> common_root("/foo/bar/baz", "/foo/bar/zip/zap")
+ PosixPath('/foo/bar')
+ >>> common_root("/foo/bar/baz", "/fod/bar/zip/zap")
+ PosixPath('/')
+ """
+ apath1 = Path(path1).absolute().parts
+ apath2 = Path(path2).absolute().parts
+ alen = min(len(apath1), len(apath2))
+ common = None
+ for a, b in zip(apath1[:alen], apath2[:alen]):
+ if a != b:
+ break
+ common = common.joinpath(a) if common else Path(a)
+ return common
+
+
+async def collect(args: Namespace):
+ """Collect test files.
+
+ Files must match the pattern ``mutest_*.py``, and their containing
+ directory must have a munet config file present. This function also changes
+ the current directory to the common parent of all the tests, and paths are
+ returned relative to the common directory.
+
+ Args:
+ args: argparse results
+
+ Returns:
+ (commondir, tests, configs): where ``commondir`` is the path representing
+ the common parent directory of all the testsd, ``tests`` is a
+ dictionary of lists of test files, keyed on their containing directory
+ path, and ``configs`` is a dictionary of config dictionaries also keyed
+ on its containing directory path. The directory paths are relative to a
+ common ancestor.
+ """
+ file_select = args.file_select
+ upaths = args.paths if args.paths else ["."]
+ globpaths = set()
+ for upath in (Path(x) for x in upaths):
+ if upath.is_file():
+ paths = {upath.absolute()}
+ else:
+ paths = {x.absolute() for x in Path(upath).rglob(file_select)}
+ globpaths |= paths
+ tests = {}
+ configs = {}
+
+ # Find the common root
+ # We don't actually need this anymore, the idea was prefix test names
+ # with uncommon paths elements to automatically differentiate them.
+ common = None
+ sortedpaths = []
+ for path in sorted(globpaths):
+ sortedpaths.append(path)
+ dirpath = path.parent
+ common = common_root(common, dirpath) if common else dirpath
+
+ ocwd = Path().absolute()
+ try:
+ os.chdir(common)
+ # Work with relative paths to the common directory
+ for path in (x.relative_to(common) for x in sortedpaths):
+ dirpath = path.parent
+ if dirpath not in configs:
+ try:
+ configs[dirpath] = get_config(search=[dirpath])
+ except FileNotFoundError:
+ logging.warning(
+ "Skipping '%s' as munet.{yaml,toml,json} not found in '%s'",
+ path,
+ dirpath,
+ )
+ continue
+ if dirpath not in tests:
+ tests[dirpath] = []
+ tests[dirpath].append(path.absolute())
+ finally:
+ os.chdir(ocwd)
+ return common, tests, configs
+
+
+async def execute_test(
+ unet: Munet,
+ test: Path,
+ args: Namespace,
+ test_num: int,
+ exec_handler: logging.Handler,
+) -> (int, int, int, Exception):
+ """Execute a test case script.
+
+ Using the built and running topology in ``unet`` for targets
+ execute the test case script file ``test``.
+
+ Args:
+ unet: a running topology.
+ test: path to the test case script file.
+ args: argparse results.
+ test_num: the number of this test case in the run.
+ exec_handler: exec file handler to add to test loggers which do not propagate.
+ """
+ test_name = testname_from_path(test)
+
+ # Get test case loggers
+ logger = logging.getLogger(f"mutest.output.{test_name}")
+ reslog = logging.getLogger(f"mutest.results.{test_name}")
+ logger.addHandler(exec_handler)
+ reslog.addHandler(exec_handler)
+
+ # We need to send an info level log to cause the speciifc handler to be
+ # created, otherwise all these debug ones don't get through
+ reslog.info("")
+
+ # reslog.debug("START: %s:%s from %s", test_num, test_name, test.stem)
+ # reslog.debug("-" * 70)
+
+ targets = dict(unet.hosts.items())
+ targets["."] = unet
+
+ tc = uapi.TestCase(
+ str(test_num), test_name, test, targets, logger, reslog, args.full_summary
+ )
+ passed, failed, e = tc.execute()
+
+ run_time = time.time() - tc.info.start_time
+
+ status = "PASS" if not (failed or e) else "FAIL"
+
+ # Turn off for now
+ reslog.debug("-" * 70)
+ reslog.debug(
+ "stats: %d steps, %d pass, %d fail, %s abort, %4.2fs elapsed",
+ passed + failed,
+ passed,
+ failed,
+ 1 if e else 0,
+ run_time,
+ )
+ reslog.debug("-" * 70)
+ reslog.debug("END: %s %s:%s\n", status, test_num, test_name)
+
+ return passed, failed, e
+
+
+def testname_from_path(path: Path) -> str:
+ """Return test name based on the path to the test file.
+
+ Args:
+ path: path to the test file.
+
+ Returns:
+ str: the name of the test.
+ """
+ return str(Path(path).stem).replace("/", ".")
+
+
+def print_header(reslog, unet):
+ targets = dict(unet.hosts.items())
+ nmax = max(len(x) for x in targets)
+ nmax = max(nmax, len("TARGET"))
+ sum_fmt = uapi.TestCase.sum_fmt.format(nmax)
+ reslog.info(sum_fmt, "NUMBER", "STAT", "TARGET", "TIME", "DESCRIPTION")
+ reslog.info("-" * 70)
+
+
+async def run_tests(args):
+ reslog = logging.getLogger("mutest.results")
+
+ common, tests, configs = await collect(args)
+ results = []
+ errlog = logging.getLogger("mutest.error")
+ reslog = logging.getLogger("mutest.results")
+ printed_header = False
+ tnum = 0
+ start_time = time.time()
+ try:
+ for dirpath in tests:
+ test_files = tests[dirpath]
+ for test in test_files:
+ tnum += 1
+ config = deepcopy(configs[dirpath])
+ test_name = testname_from_path(test)
+ rundir = args.rundir.joinpath(test_name)
+
+ # Add an test case exec file handler to the root logger and result
+ # logger
+ exec_path = rundir.joinpath("mutest-exec.log")
+ exec_path.parent.mkdir(parents=True, exist_ok=True)
+ exec_handler = logging.FileHandler(exec_path, "w")
+ exec_handler.setFormatter(exec_formatter)
+ root_logger.addHandler(exec_handler)
+
+ try:
+ async for unet in get_unet(config, common, rundir):
+ if not printed_header:
+ print_header(reslog, unet)
+ printed_header = True
+ passed, failed, e = await execute_test(
+ unet, test, args, tnum, exec_handler
+ )
+ except KeyboardInterrupt as error:
+ errlog.warning("KeyboardInterrupt while running test %s", test_name)
+ passed, failed, e = 0, 0, error
+ raise
+ except Exception as error:
+ logging.error(
+ "Error executing test %s: %s", test, error, exc_info=True
+ )
+ errlog.error(
+ "Error executing test %s: %s", test, error, exc_info=True
+ )
+ passed, failed, e = 0, 0, error
+ finally:
+ # Remove the test case exec file handler form the root logger.
+ root_logger.removeHandler(exec_handler)
+ results.append((test_name, passed, failed, e))
+
+ except KeyboardInterrupt:
+ pass
+
+ run_time = time.time() - start_time
+ tnum = 0
+ tpassed = 0
+ tfailed = 0
+ texc = 0
+
+ spassed = 0
+ sfailed = 0
+
+ for result in results:
+ _, passed, failed, e = result
+ tnum += 1
+ spassed += passed
+ sfailed += failed
+ if e:
+ texc += 1
+ if failed or e:
+ tfailed += 1
+ else:
+ tpassed += 1
+
+ reslog.info("")
+ reslog.info(
+ "run stats: %s steps, %s pass, %s fail, %s abort, %4.2fs elapsed",
+ spassed + sfailed,
+ spassed,
+ sfailed,
+ texc,
+ run_time,
+ )
+ reslog.info("-" * 70)
+
+ tnum = 0
+ for result in results:
+ test_name, passed, failed, e = result
+ tnum += 1
+ s = "FAIL" if failed or e else "PASS"
+ reslog.info(" %s %s:%s", s, tnum, test_name)
+
+ reslog.info("-" * 70)
+ reslog.info(
+ "END RUN: %s test scripts, %s passed, %s failed", tnum, tpassed, tfailed
+ )
+
+ return 1 if tfailed else 0
+
+
+async def async_main(args):
+ status = 3
+ try:
+ # For some reson we are not catching exceptions raised inside
+ status = await run_tests(args)
+ except KeyboardInterrupt:
+ logging.info("Exiting (async_main), received KeyboardInterrupt in main")
+ except Exception as error:
+ logging.info(
+ "Exiting (async_main), unexpected exception %s", error, exc_info=True
+ )
+ logging.debug("async_main returns %s", status)
+ return status
+
+
+def main():
+ ap = ArgumentParser()
+ ap.add_argument(
+ "--dist",
+ type=int,
+ nargs="?",
+ const=-1,
+ default=0,
+ action="store",
+ metavar="NUM-THREADS",
+ help="Run in parallel, value is num. of threads or no value for auto",
+ )
+ ap.add_argument("-d", "--rundir", help="runtime directory for tempfiles, logs, etc")
+ ap.add_argument(
+ "--file-select", default="mutest_*.py", help="shell glob for finding tests"
+ )
+ ap.add_argument("--log-config", help="logging config file (yaml, toml, json, ...)")
+ ap.add_argument(
+ "-V",
+ "--full-summary",
+ action="store_true",
+ help="print full summary headers from docstrings",
+ )
+ ap.add_argument(
+ "-v", dest="verbose", action="count", default=0, help="More -v's, more verbose"
+ )
+ ap.add_argument("paths", nargs="*", help="Paths to collect tests from")
+ args = ap.parse_args()
+
+ rundir = args.rundir if args.rundir else "/tmp/mutest"
+ args.rundir = Path(rundir)
+ os.environ["MUNET_RUNDIR"] = rundir
+ subprocess.run(f"mkdir -p {rundir} && chmod 755 {rundir}", check=True, shell=True)
+
+ config = parser.setup_logging(args, config_base="logconf-mutest")
+ # Grab the exec formatter from the logging config
+ if fconfig := config.get("formatters", {}).get("exec"):
+ global exec_formatter # pylint: disable=W291,W0603
+ exec_formatter = logging.Formatter(
+ fconfig.get("format"), fconfig.get("datefmt")
+ )
+
+ loop = None
+ status = 4
+ try:
+ loop = get_event_loop()
+ status = loop.run_until_complete(async_main(args))
+ except KeyboardInterrupt:
+ logging.info("Exiting (main), received KeyboardInterrupt in main")
+ except Exception as error:
+ logging.info("Exiting (main), unexpected exception %s", error, exc_info=True)
+ finally:
+ if loop:
+ loop.close()
+
+ sys.exit(status)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/topotests/munet/mutest/userapi.py b/tests/topotests/munet/mutest/userapi.py
new file mode 100644
index 0000000000..1df8c0d012
--- /dev/null
+++ b/tests/topotests/munet/mutest/userapi.py
@@ -0,0 +1,1111 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright 2017, 2022, LabN Consulting, L.L.C.
+"""Mutest is a simple send/expect based testing framework.
+
+This module implements the basic send/expect functionality for mutest. The test
+developer first creates a munet topology (:ref:`munet-config`) and then writes test
+scripts ("test cases") which are composed of calls to the functions defined below
+("steps"). In short these are:
+
+Send/Expect functions:
+
+ - :py:func:`step`
+
+ - :py:func:`step_json`
+
+ - :py:func:`match_step`
+
+ - :py:func:`match_step_json`
+
+ - :py:func:`wait_step`
+
+ - :py:func:`wait_step_json`
+
+Control/Utility functions:
+
+ - :py:func:`script_dir`
+
+ - :py:func:`include`
+
+ - :py:func:`log`
+
+ - :py:func:`test`
+
+Test scripts are located by the :command:`mutest` command by their name. The name of a
+test script should take the form ``mutest_TESTNAME.py`` where ``TESTNAME`` is replaced
+with a user chosen name for the test case.
+
+Here's a simple example test script which first checks that a specific forwarding entry
+is in the FIB for the IP destination ``10.0.1.1``. Then it checks repeatedly for up to
+10 seconds for a second forwarding entry in the FIB for the IP destination ``10.0.2.1``.
+
+.. code-block:: python
+
+ match_step("r1", 'vtysh -c "show ip fib 10.0.1.1"', "Routing entry for 10.0.1.0/24",
+ "Check for FIB entry for 10.0.1.1")
+
+ wait_step("r1",
+ 'vtysh -c "show ip fib 10.0.2.1"',
+ "Routing entry for 10.0.2.0/24",
+ desc="Check for FIB entry for 10.0.2.1",
+ timeout=10)
+
+Notice that the call arguments can be specified by their correct position in the list or
+using keyword names, and they can also be specified over multiple lines if preferred.
+
+All of the functions are documented and defined below.
+"""
+
+# pylint: disable=global-statement
+
+import functools
+import json
+import logging
+import pprint
+import re
+import time
+
+from pathlib import Path
+from typing import Any
+from typing import Union
+
+from deepdiff import DeepDiff as json_cmp
+
+from munet.base import Commander
+
+
+class TestCaseInfo:
+ """Object to hold nestable TestCase Results."""
+
+ def __init__(self, tag: str, name: str, path: Path):
+ self.path = path.absolute()
+ self.tag = tag
+ self.name = name
+ self.steps = 0
+ self.passed = 0
+ self.failed = 0
+ self.start_time = time.time()
+ self.step_start_time = self.start_time
+ self.run_time = None
+
+ def __repr__(self):
+ return (
+ f"TestCaseInfo({self.tag} {self.name} steps {self.steps} "
+ f"p {self.passed} f {self.failed} path {self.path})"
+ )
+
+
+class TestCase:
+ """A mutest testcase.
+
+ This is normally meant to be used internally by the mutest command to
+ implement the user API. See README-mutest.org for usage details on the
+ user API.
+
+ Args:
+ tag: identity of the test in a run. (x.x...)
+ name: the name of the test case
+ path: the test file that is being executed.
+ targets: a dictionary of objects which implement ``cmd_nostatus(str)``
+ output_logger: a logger for output and other messages from the test.
+ result_logger: a logger to output the results of test steps to.
+ full_summary: if True then print entire doctstring instead of
+ only the first line in the results report
+
+ Attributes:
+ tag: identity of the test in a run
+ name: the name of the test
+ targets: dictionary of targets.
+
+ steps: total steps executed so far.
+ passed: number of passing steps.
+ failed: number of failing steps.
+
+ last: the last command output.
+ last_m: the last result of re.search during a matching step on the output with
+ newlines converted to spaces.
+
+ :meta private:
+ """
+
+ # sum_hfmt = "{:5.5s} {:4.4s} {:>6.6s} {}"
+ # sum_dfmt = "{:5s} {:4.4s} {:^6.6s} {}"
+ sum_fmt = "%-8.8s %4.4s %{}s %6s %s"
+
+ def __init__(
+ self,
+ tag: int,
+ name: str,
+ path: Path,
+ targets: dict,
+ output_logger: logging.Logger = None,
+ result_logger: logging.Logger = None,
+ full_summary: bool = False,
+ ):
+
+ self.info = TestCaseInfo(tag, name, path)
+ self.__saved_info = []
+ self.__short_doc_header = not full_summary
+
+ self.__space_before_result = False
+
+ # we are only ever in a section once, an include ends a section
+ # so are never in section+include, and another section ends a
+ # section, so we don't need __in_section to be save in the
+ # TestCaseInfo struct.
+ self.__in_section = False
+
+ self.targets = targets
+
+ self.last = ""
+ self.last_m = None
+
+ self.rlog = result_logger
+ self.olog = output_logger
+ self.logf = functools.partial(self.olog.log, logging.INFO)
+
+ oplog = logging.getLogger("mutest.oper")
+ self.oplogf = oplog.debug
+ self.oplogf("new TestCase: tag: %s name: %s path: %s", tag, name, path)
+
+ # find the longerst target name and make target field that wide
+ nmax = max(len(x) for x in targets)
+ nmax = max(nmax, len("TARGET"))
+ self.sum_fmt = TestCase.sum_fmt.format(nmax)
+
+ # Let's keep this out of summary for now
+ self.rlog.debug(self.sum_fmt, "NUMBER", "STAT", "TARGET", "TIME", "DESCRIPTION")
+ self.rlog.debug("-" * 70)
+
+ @property
+ def tag(self):
+ return self.info.tag
+
+ @property
+ def name(self):
+ return self.info.name
+
+ @property
+ def steps(self):
+ return self.info.steps
+
+ @property
+ def passed(self):
+ return self.info.passed
+
+ @property
+ def failed(self):
+ return self.info.failed
+
+ def execute(self):
+ """Execute the test case.
+
+ :meta private:
+ """
+ assert TestCase.g_tc is None
+ self.oplogf("execute")
+ try:
+ TestCase.g_tc = self
+ e = self.__exec_script(self.info.path, True, False)
+ except BaseException:
+ self.__end_test()
+ raise
+ return *self.__end_test(), e
+
+ def __del__(self):
+ if TestCase.g_tc is self:
+ logging.error("Internal error, TestCase.__end_test() was not called!")
+ TestCase.g_tc = None
+
+ def __push_execinfo(self, path: Path):
+ self.oplogf(
+ "__push_execinfo: path: %s current top is %s",
+ path,
+ pprint.pformat(self.info),
+ )
+ newname = self.name + path.stem
+ self.info.steps += 1
+ self.__saved_info.append(self.info)
+ tag = f"{self.info.tag}.{self.info.steps}"
+ self.info = TestCaseInfo(tag, newname, path)
+ self.oplogf("__push_execinfo: now on top: %s", pprint.pformat(self.info))
+
+ def __pop_execinfo(self):
+ # do something with tag?
+ finished_info = self.info
+ self.info = self.__saved_info.pop()
+ self.oplogf(" __pop_execinfo: poppped: %s", pprint.pformat(finished_info))
+ self.oplogf(" __pop_execinfo: now on top: %s", pprint.pformat(self.info))
+ return finished_info
+
+ def __print_header(self, tag, header, add_newline=False):
+ # self.olog.info(self.sum_fmt, tag, "", "", "", header)
+ self.olog.info("== %s ==", f"TEST: {tag}. {header}")
+ if add_newline:
+ self.rlog.info("")
+ self.rlog.info("%s. %s", tag, header)
+
+ def __exec_script(self, path, print_header, add_newline):
+
+ # Below was the original method to avoid the global TestCase
+ # variable; however, we need global functions so we can import them
+ # into test scripts. Without imports pylint will complain about undefined
+ # functions and the resulting christmas tree of warnings is annoying.
+ #
+ # pylint: disable=possibly-unused-variable,exec-used,redefined-outer-name
+ # include = self.include
+ # log = self.logf
+ # match_step = self.match_step
+ # match_step_json = self.match_step_json
+ # step = self.step
+ # step_json = self.step_json
+ # test = self.test
+ # wait_step = self.wait_step
+ # wait_step_json = self.wait_step_json
+
+ name = f"{path.stem}{self.tag}"
+ name = re.sub(r"\W|^(?=\d)", "_", name)
+
+ _ok_result = "marker"
+ try:
+ self.oplogf("__exec_script: path %s", path)
+ script = open(path, "r", encoding="utf-8").read()
+
+ # Load the script into a function.
+ script = script.strip()
+ s2 = (
+ # f"async def _{name}(ok_result):\n"
+ f"def _{name}(ok_result):\n"
+ + " "
+ + script.replace("\n", "\n ")
+ + "\n return ok_result\n"
+ + "\n"
+ )
+ exec(s2)
+
+ # Extract any docstring as a title.
+ if print_header:
+ title = locals()[f"_{name}"].__doc__.lstrip()
+ if self.__short_doc_header and (title := title.lstrip()):
+ if (idx := title.find("\n")) != -1:
+ title = title[:idx].strip()
+ if not title:
+ title = f"Test from file: {self.info.path.name}"
+ self.__print_header(self.info.tag, title, add_newline)
+ self.__space_before_result = False
+
+ # Execute the function.
+ result = locals()[f"_{name}"](_ok_result)
+
+ # Here's where we can do async in the future if we want.
+ # result = await locals()[f"_{name}"](_ok_result)
+ except Exception as error:
+ logging.error(
+ "Unexpected exception executing %s: %s", name, error, exc_info=True
+ )
+ return error
+ else:
+ if result is not _ok_result:
+ logging.info("%s returned early, result: %s", name, result)
+ else:
+ self.oplogf("__exec_script: name %s completed normally", name)
+ return None
+
+ def __post_result(self, target, success, rstr, logstr=None):
+ self.oplogf(
+ "__post_result: target: %s success %s rstr %s", target, success, rstr
+ )
+ if success:
+ self.info.passed += 1
+ status = "PASS"
+ outlf = self.logf
+ reslf = self.rlog.info
+ else:
+ self.info.failed += 1
+ status = "FAIL"
+ outlf = self.olog.warning
+ reslf = self.rlog.warning
+
+ self.info.steps += 1
+ if logstr is not None:
+ outlf("R:%d %s: %s" % (self.steps, status, logstr))
+
+ run_time = time.time() - self.info.step_start_time
+
+ stepstr = f"{self.tag}.{self.steps}"
+ rtimes = _delta_time_str(run_time)
+
+ if self.__space_before_result:
+ self.rlog.info("")
+ self.__space_before_result = False
+
+ reslf(self.sum_fmt, stepstr, status, target, rtimes, rstr)
+
+ # start counting for next step now
+ self.info.step_start_time = time.time()
+
+ def __end_test(self) -> (int, int):
+ """End the test log final results.
+
+ Returns:
+ number of steps, number passed, number failed, run time.
+ """
+ self.oplogf("__end_test: __in_section: %s", self.__in_section)
+ if self.__in_section:
+ self.__end_section()
+
+ passed, failed = self.info.passed, self.info.failed
+
+ # No close for loggers
+ # self.olog.close()
+ # self.rlog.close()
+ self.olog = None
+ self.rlog = None
+
+ assert (
+ TestCase.g_tc == self
+ ), "TestCase global unexpectedly someon else in __end_test"
+ TestCase.g_tc = None
+
+ self.info.run_time = time.time() - self.info.start_time
+ return passed, failed
+
+ def _command(
+ self,
+ target: str,
+ cmd: str,
+ ) -> str:
+ """Execute a ``cmd`` and return result.
+
+ Args:
+ target: the target to execute the command on.
+ cmd: string to execut on the target.
+ """
+ out = self.targets[target].cmd_nostatus(cmd, warn=False)
+ self.last = out = out.rstrip()
+ report = out if out else "<no output>"
+ self.logf("COMMAND OUTPUT:\n%s", report)
+ return out
+
+ def _command_json(
+ self,
+ target: str,
+ cmd: str,
+ ) -> dict:
+ """Execute a json ``cmd`` and return json result.
+
+ Args:
+ target: the target to execute the command on.
+ cmd: string to execut on the target.
+ """
+ out = self.targets[target].cmd_nostatus(cmd, warn=False)
+ self.last = out = out.rstrip()
+ try:
+ js = json.loads(out)
+ except Exception as error:
+ js = {}
+ self.olog.warning(
+ "JSON load failed. Check command output is in JSON format: %s",
+ error,
+ )
+ self.logf("COMMAND OUTPUT:\n%s", out)
+ return js
+
+ def _match_command(
+ self,
+ target: str,
+ cmd: str,
+ match: str,
+ expect_fail: bool,
+ flags: int,
+ ) -> (bool, Union[str, list]):
+ """Execute a ``cmd`` and check result.
+
+ Args:
+ target: the target to execute the command on.
+ cmd: string to execute on the target.
+ match: regex to ``re.search()`` for in output.
+ expect_fail: if True then succeed when the regexp doesn't match.
+ flags: python regex flags to modify matching behavior
+
+ Returns:
+ (success, matches): if the match fails then "matches" will be None,
+ otherwise if there were matching groups then groups() will be returned in
+ ``matches`` otherwise group(0) (i.e., the matching text).
+ """
+ out = self._command(target, cmd)
+ search = re.search(match, out, flags)
+ self.last_m = search
+ if search is None:
+ success = expect_fail
+ ret = None
+ else:
+ success = not expect_fail
+ ret = search.groups()
+ if not ret:
+ ret = search.group(0)
+
+ level = logging.DEBUG if success else logging.WARNING
+ self.olog.log(level, "matched:%s:", ret)
+ return success, ret
+
+ def _match_command_json(
+ self,
+ target: str,
+ cmd: str,
+ match: Union[str, dict],
+ expect_fail: bool,
+ ) -> Union[str, dict]:
+ """Execute a json ``cmd`` and check result.
+
+ Args:
+ target: the target to execute the command on.
+ cmd: string to execut on the target.
+ match: A json ``str`` or object (``dict``) to compare against the json
+ output from ``cmd``.
+ expect_fail: if True then succeed when the json doesn't match.
+ """
+ js = self._command_json(target, cmd)
+ try:
+ expect = json.loads(match)
+ except Exception as error:
+ expect = {}
+ self.olog.warning(
+ "JSON load failed. Check match value is in JSON format: %s", error
+ )
+
+ if json_diff := json_cmp(expect, js):
+ success = expect_fail
+ if not success:
+ self.logf("JSON DIFF:%s:" % json_diff)
+ return success, json_diff
+
+ success = not expect_fail
+ return success, js
+
+ def _wait(
+ self,
+ target: str,
+ cmd: str,
+ match: Union[str, dict],
+ is_json: bool,
+ timeout: float,
+ interval: float,
+ expect_fail: bool,
+ flags: int,
+ ) -> Union[str, dict]:
+ """Execute a command repeatedly waiting for result until timeout."""
+ startt = time.time()
+ endt = startt + timeout
+
+ success = False
+ ret = None
+ while not success and time.time() < endt:
+ if is_json:
+ success, ret = self._match_command_json(target, cmd, match, expect_fail)
+ else:
+ success, ret = self._match_command(
+ target, cmd, match, expect_fail, flags
+ )
+ if not success:
+ time.sleep(interval)
+ return success, ret
+
+ # ---------------------
+ # Public APIs for User
+ # ---------------------
+
+ def include(self, pathname: str, new_section: bool = False):
+ """See :py:func:`~munet.mutest.userapi.include`.
+
+ :meta private:
+ """
+ path = Path(pathname)
+ path = self.info.path.parent.joinpath(path)
+
+ self.oplogf(
+ "include: new path: %s create section: %s currently __in_section: %s",
+ path,
+ new_section,
+ self.__in_section,
+ )
+
+ if new_section:
+ self.oplogf("include: starting new exec section")
+ self.__start_exec_section(path)
+ our_info = self.info
+ # Note we do *not* mark __in_section True
+ else:
+ # swap the current path inside the top info
+ old_path = self.info.path
+ self.info.path = path
+ self.oplogf("include: swapped info path: new %s old %s", path, old_path)
+
+ self.__exec_script(path, print_header=new_section, add_newline=new_section)
+
+ if new_section:
+ # Something within the section creating include has also created a section
+ # end it, sections do not cross section creating file boundaries
+ if self.__in_section:
+ self.oplogf(
+ "include done: path: %s __in_section calling __end_section", path
+ )
+ self.__end_section()
+
+ # We should now be back to the info we started with, b/c we don't actually
+ # start a new section (__in_section) that then could have been ended inside
+ # the included file.
+ assert our_info == self.info
+
+ self.oplogf(
+ "include done: path: %s new_section calling __end_section", path
+ )
+ self.__end_section()
+ else:
+ # The current top path could be anything due to multiple inline includes as
+ # well as section swap in and out. Forcibly return the top path to the file
+ # we are returning to
+ self.info.path = old_path
+ self.oplogf("include: restored info path: %s", old_path)
+
+ def __end_section(self):
+ self.oplogf("__end_section: __in_section: %s", self.__in_section)
+ info = self.__pop_execinfo()
+ passed, failed = info.passed, info.failed
+ self.info.passed += passed
+ self.info.failed += failed
+ self.__space_before_result = True
+ self.oplogf("__end_section setting __in_section to False")
+ self.__in_section = False
+
+ def __start_exec_section(self, path):
+ self.oplogf("__start_exec_section: __in_section: %s", self.__in_section)
+ if self.__in_section:
+ self.__end_section()
+
+ self.__push_execinfo(path)
+ self.__space_before_result = False
+ self.oplogf("NOT setting __in_section to True")
+ assert not self.__in_section
+
+ def section(self, desc: str):
+ """See :py:func:`~munet.mutest.userapi.section`.
+
+ :meta private:
+ """
+ self.oplogf("section: __in_section: %s", self.__in_section)
+ # Grab path before we pop the current info off the top
+ path = self.info.path
+ old_steps = self.info.steps
+
+ if self.__in_section:
+ self.__end_section()
+
+ self.__push_execinfo(path)
+ add_nl = self.info.steps <= old_steps
+
+ self.__space_before_result = False
+ self.__in_section = True
+ self.oplogf(" section setting __in_section to True")
+ self.__print_header(self.info.tag, desc, add_nl)
+
+ def step(self, target: str, cmd: str) -> str:
+ """See :py:func:`~munet.mutest.userapi.step`.
+
+ :meta private:
+ """
+ self.logf(
+ "#%s.%s:%s:STEP:%s:%s",
+ self.tag,
+ self.steps + 1,
+ self.info.path,
+ target,
+ cmd,
+ )
+ return self._command(target, cmd)
+
+ def step_json(self, target: str, cmd: str) -> dict:
+ """See :py:func:`~munet.mutest.userapi.step_json`.
+
+ :meta private:
+ """
+ self.logf(
+ "#%s.%s:%s:STEP_JSON:%s:%s",
+ self.tag,
+ self.steps + 1,
+ self.info.path,
+ target,
+ cmd,
+ )
+ return self._command_json(target, cmd)
+
+ def match_step(
+ self,
+ target: str,
+ cmd: str,
+ match: str,
+ desc: str = "",
+ expect_fail: bool = False,
+ flags: int = re.DOTALL,
+ ) -> (bool, Union[str, list]):
+ """See :py:func:`~munet.mutest.userapi.match_step`.
+
+ :meta private:
+ """
+ self.logf(
+ "#%s.%s:%s:MATCH_STEP:%s:%s:%s:%s:%s:%s",
+ self.tag,
+ self.steps + 1,
+ self.info.path,
+ target,
+ cmd,
+ match,
+ desc,
+ expect_fail,
+ flags,
+ )
+ success, ret = self._match_command(target, cmd, match, expect_fail, flags)
+ if desc:
+ self.__post_result(target, success, desc)
+ return success, ret
+
+ def test_step(self, expr_or_value: Any, desc: str, target: str = "") -> bool:
+ """See :py:func:`~munet.mutest.userapi.test`.
+
+ :meta private:
+ """
+ success = bool(expr_or_value)
+ self.__post_result(target, success, desc)
+ return success
+
+ def match_step_json(
+ self,
+ target: str,
+ cmd: str,
+ match: Union[str, dict],
+ desc: str = "",
+ expect_fail: bool = False,
+ ) -> (bool, Union[str, dict]):
+ """See :py:func:`~munet.mutest.userapi.match_step_json`.
+
+ :meta private:
+ """
+ self.logf(
+ "#%s.%s:%s:MATCH_STEP_JSON:%s:%s:%s:%s:%s",
+ self.tag,
+ self.steps + 1,
+ self.info.path,
+ target,
+ cmd,
+ match,
+ desc,
+ expect_fail,
+ )
+ success, ret = self._match_command_json(target, cmd, match, expect_fail)
+ if desc:
+ self.__post_result(target, success, desc)
+ return success, ret
+
+ def wait_step(
+ self,
+ target: str,
+ cmd: str,
+ match: Union[str, dict],
+ desc: str = "",
+ timeout=10,
+ interval=0.5,
+ expect_fail: bool = False,
+ flags: int = re.DOTALL,
+ ) -> (bool, Union[str, list]):
+ """See :py:func:`~munet.mutest.userapi.wait_step`.
+
+ :meta private:
+ """
+ if interval is None:
+ interval = min(timeout / 20, 0.25)
+ self.logf(
+ "#%s.%s:%s:WAIT_STEP:%s:%s:%s:%s:%s:%s:%s:%s",
+ self.tag,
+ self.steps + 1,
+ self.info.path,
+ target,
+ cmd,
+ match,
+ timeout,
+ interval,
+ desc,
+ expect_fail,
+ flags,
+ )
+ success, ret = self._wait(
+ target, cmd, match, False, timeout, interval, expect_fail, flags
+ )
+ if desc:
+ self.__post_result(target, success, desc)
+ return success, ret
+
+ def wait_step_json(
+ self,
+ target: str,
+ cmd: str,
+ match: Union[str, dict],
+ desc: str = "",
+ timeout=10,
+ interval=None,
+ expect_fail: bool = False,
+ ) -> (bool, Union[str, dict]):
+ """See :py:func:`~munet.mutest.userapi.wait_step_json`.
+
+ :meta private:
+ """
+ if interval is None:
+ interval = min(timeout / 20, 0.25)
+ self.logf(
+ "#%s.%s:%s:WAIT_STEP:%s:%s:%s:%s:%s:%s:%s",
+ self.tag,
+ self.steps + 1,
+ self.info.path,
+ target,
+ cmd,
+ match,
+ timeout,
+ interval,
+ desc,
+ expect_fail,
+ )
+ success, ret = self._wait(
+ target, cmd, match, True, timeout, interval, expect_fail, 0
+ )
+ if desc:
+ self.__post_result(target, success, desc)
+ return success, ret
+
+
+# A non-rentrant global to allow for simplified operations
+TestCase.g_tc = None
+
+# pylint: disable=protected-access
+
+
+def _delta_time_str(run_time: float) -> str:
+ if run_time < 0.0001:
+ return "0.0"
+ if run_time < 0.001:
+ return f"{run_time:1.4f}"
+ if run_time < 0.01:
+ return f"{run_time:2.3f}"
+ if run_time < 0.1:
+ return f"{run_time:3.2f}"
+ if run_time < 100:
+ return f"{run_time:4.1f}"
+ return f"{run_time:5f}s"
+
+
+def section(desc: str):
+ """Start a new section for steps, with a description.
+
+ This starts a new section of tests. The result is basically
+ the same as doing a non-inline include. The current test number
+ is used to form a new sub-set of test steps. So if the current
+ test number is 2.3, a section will now number subsequent steps
+ 2.3.1, 2.3.2, ...
+
+ A subsequent :py:func:`section` or non-inline :py:func:`include`
+ call ends the current section and advances the base test number.
+
+ Args:
+ desc: the description for the new section.
+ """
+ TestCase.g_tc.section(desc)
+
+
+def log(fmt, *args, **kwargs):
+ """Log a message in the testcase output log."""
+ return TestCase.g_tc.logf(fmt, *args, **kwargs)
+
+
+def include(pathname: str, new_section=False):
+ """Include a file as part of testcase.
+
+ Args:
+ pathname: the file to include.
+ new_section: if a new section should be created, otherwise
+ commands are executed inline.
+ """
+ return TestCase.g_tc.include(pathname, new_section)
+
+
+def script_dir() -> Path:
+ """The pathname to the directory containing the current script file.
+
+ When an include() is called the script_dir is updated to be current with the
+ includeded file, and is reverted to the previous value when the include completes.
+ """
+ return TestCase.g_tc.info.path.parent
+
+
+def get_target(name: str) -> Commander:
+ """Get the target object with the given ``name``."""
+ return TestCase.g_tc.targets[name]
+
+
+def step(target: str, cmd: str) -> str:
+ """Execute a ``cmd`` on a ``target`` and return the output.
+
+ Args:
+ target: the target to execute the ``cmd`` on.
+ cmd: string to execute on the target.
+
+ Returns:
+ Returns the ``str`` output of the ``cmd``.
+ """
+ return TestCase.g_tc.step(target, cmd)
+
+
+def step_json(target: str, cmd: str) -> dict:
+ """Execute a json ``cmd`` on a ``target`` and return the json object.
+
+ Args:
+ target: the target to execute the ``cmd`` on.
+ cmd: string to execute on the target.
+
+ Returns:
+ Returns the json object after parsing the ``cmd`` output.
+
+ If json parse fails, a warning is logged and an empty ``dict`` is used.
+ """
+ return TestCase.g_tc.step_json(target, cmd)
+
+
+def test_step(expr_or_value: Any, desc: str, target: str = "") -> bool:
+ """Evaluates ``expr_or_value`` and posts a result base on it bool(expr).
+
+ If ``expr_or_value`` evaluates to a positive result (i.e., True, non-zero, non-None,
+ non-empty string, non-empty list, etc..) then a PASS result is recorded, otherwise
+ record a FAIL is recorded.
+
+ Args:
+ expr: an expression or value to evaluate
+ desc: description of this test step.
+ target: optional target to associate with this test in the result string.
+
+ Returns:
+ A bool indicating the test PASS or FAIL result.
+ """
+ return TestCase.g_tc.test_step(expr_or_value, desc, target)
+
+
+def match_step(
+ target: str,
+ cmd: str,
+ match: str,
+ desc: str = "",
+ expect_fail: bool = False,
+ flags: int = re.DOTALL,
+) -> (bool, Union[str, list]):
+ """Execute a ``cmd`` on a ``target`` check result.
+
+ Execute ``cmd`` on ``target`` and check if the regexp in ``match``
+ matches or doesn't match (according to the ``expect_fail`` value) the
+ ``cmd`` output.
+
+ If the ``match`` regexp includes groups and if the match succeeds
+ the group values will be returned in a list, otherwise the command
+ output is returned.
+
+ Args:
+ target: the target to execute the ``cmd`` on.
+ cmd: string to execut on the ``target``.
+ match: regex to match against output.
+ desc: description of test, if no description then no result is logged.
+ expect_fail: if True then succeed when the regexp doesn't match.
+ flags: python regex flags to modify matching behavior
+
+ Returns:
+ Returns a 2-tuple. The first value is a bool indicating ``success``.
+ The second value will be a list from ``re.Match.groups()`` if non-empty,
+ otherwise ``re.Match.group(0)`` if there was a match otherwise None.
+ """
+ return TestCase.g_tc.match_step(target, cmd, match, desc, expect_fail, flags)
+
+
+def match_step_json(
+ target: str,
+ cmd: str,
+ match: Union[str, dict],
+ desc: str = "",
+ expect_fail: bool = False,
+) -> (bool, Union[str, dict]):
+ """Execute a ``cmd`` on a ``target`` check result.
+
+ Execute ``cmd`` on ``target`` and check if the json object in ``match``
+ matches or doesn't match (according to the ``expect_fail`` value) the
+ json output from ``cmd``.
+
+ Args:
+ target: the target to execute the ``cmd`` on.
+ cmd: string to execut on the ``target``.
+ match: A json ``str`` or object (``dict``) to compare against the json
+ output from ``cmd``.
+ desc: description of test, if no description then no result is logged.
+ expect_fail: if True then succeed if the a json doesn't match.
+
+ Returns:
+ Returns a 2-tuple. The first value is a bool indicating ``success``. The
+ second value is a ``str`` diff if there is a difference found in the json
+ compare, otherwise the value is the json object (``dict``) from the ``cmd``.
+
+ If json parse fails, a warning is logged and an empty ``dict`` is used.
+ """
+ return TestCase.g_tc.match_step_json(target, cmd, match, desc, expect_fail)
+
+
+def wait_step(
+ target: str,
+ cmd: str,
+ match: Union[str, dict],
+ desc: str = "",
+ timeout: float = 10.0,
+ interval: float = 0.5,
+ expect_fail: bool = False,
+ flags: int = re.DOTALL,
+) -> (bool, Union[str, list]):
+ """Execute a ``cmd`` on a ``target`` repeatedly, looking for a result.
+
+ Execute ``cmd`` on ``target``, every ``interval`` seconds for up to ``timeout``
+ seconds until the output of ``cmd`` does or doesn't match (according to the
+ ``expect_fail`` value) the ``match`` value.
+
+ Args:
+ target: the target to execute the ``cmd`` on.
+ cmd: string to execut on the ``target``.
+ match: regexp to match against output.
+ timeout: The number of seconds to repeat the ``cmd`` looking for a match
+ (or non-match if ``expect_fail`` is True).
+ interval: The number of seconds between running the ``cmd``. If not
+ specified the value is calculated from the timeout value so that on
+ average the cmd will execute 10 times. The minimum calculated interval
+ is .25s, shorter values can be passed explicitly.
+ desc: description of test, if no description then no result is logged.
+ expect_fail: if True then succeed when the regexp *doesn't* match.
+ flags: python regex flags to modify matching behavior
+
+ Returns:
+ Returns a 2-tuple. The first value is a bool indicating ``success``.
+ The second value will be a list from ``re.Match.groups()`` if non-empty,
+ otherwise ``re.Match.group(0)`` if there was a match otherwise None.
+ """
+ return TestCase.g_tc.wait_step(
+ target, cmd, match, desc, timeout, interval, expect_fail, flags
+ )
+
+
+def wait_step_json(
+ target: str,
+ cmd: str,
+ match: Union[str, dict],
+ desc: str = "",
+ timeout=10,
+ interval=None,
+ expect_fail: bool = False,
+) -> (bool, Union[str, dict]):
+ """Execute a cmd repeatedly and wait for matching result.
+
+ Execute ``cmd`` on ``target``, every ``interval`` seconds until
+ the output of ``cmd`` matches or doesn't match (according to the
+ ``expect_fail`` value) ``match``, for up to ``timeout`` seconds.
+
+ ``match`` is a regular expression to search for in the output of ``cmd`` when
+ ``is_json`` is False.
+
+ When ``is_json`` is True ``match`` must be a json object or a ``str`` which
+ parses into a json object. Likewise, the ``cmd`` output is parsed into a json
+ object and then a comparison is done between the two json objects.
+
+ Args:
+ target: the target to execute the ``cmd`` on.
+ cmd: string to execut on the ``target``.
+ match: A json object or str representation of one to compare against json
+ output from ``cmd``.
+ desc: description of test, if no description then no result is logged.
+ timeout: The number of seconds to repeat the ``cmd`` looking for a match
+ (or non-match if ``expect_fail`` is True).
+ interval: The number of seconds between running the ``cmd``. If not
+ specified the value is calculated from the timeout value so that on
+ average the cmd will execute 10 times. The minimum calculated interval
+ is .25s, shorter values can be passed explicitly.
+ expect_fail: if True then succeed if the a json doesn't match.
+
+ Returns:
+ Returns a 2-tuple. The first value is a bool indicating ``success``.
+ The second value is a ``str`` diff if there is a difference found in the
+ json compare, otherwise the value is a json object (dict) from the ``cmd``
+ output.
+
+ If json parse fails, a warning is logged and an empty ``dict`` is used.
+ """
+ return TestCase.g_tc.wait_step_json(
+ target, cmd, match, desc, timeout, interval, expect_fail
+ )
+
+
+def luInclude(filename, CallOnFail=None):
+ """Backward compatible API, do not use in new tests."""
+ return include(filename)
+
+
+def luLast(usenl=False):
+ """Backward compatible API, do not use in new tests."""
+ del usenl
+ return TestCase.g_tc.last_m
+
+
+def luCommand(
+ target,
+ cmd,
+ regexp=".",
+ op="none",
+ result="",
+ ltime=10,
+ returnJson=False,
+ wait_time=0.5,
+):
+ """Backward compatible API, do not use in new tests.
+
+ Only non-json is verified to any degree of confidence by code inspection.
+
+ For non-json should return match.group() if match else return bool(op == "fail").
+
+ For json if no diff return the json else diff return bool(op == "jsoncmp_fail")
+ bug if no json from output (fail parse) could maybe generate diff, which could
+ then return
+ """
+ if op == "wait":
+ if returnJson:
+ return wait_step_json(target, cmd, regexp, result, ltime, wait_time)
+
+ success, _ = wait_step(target, cmd, regexp, result, ltime, wait_time)
+ match = luLast()
+ if success and match is not None:
+ return match.group()
+ return success
+
+ if op == "none":
+ if returnJson:
+ return step_json(target, cmd)
+ return step(target, cmd)
+
+ if returnJson and op in ("jsoncmp_fail", "jsoncmp_pass"):
+ expect_fail = op == "jsoncmp_fail"
+ return match_step_json(target, cmd, regexp, result, expect_fail)
+
+ assert not returnJson
+ assert op in ("fail", "pass")
+ expect_fail = op == "fail"
+ success, _ = match_step(target, cmd, regexp, result, expect_fail)
+ match = luLast()
+ if success and match is not None:
+ return match.group()
+ return success
diff --git a/tests/topotests/munet/mutestshare.py b/tests/topotests/munet/mutestshare.py
new file mode 100644
index 0000000000..95ffa74e7b
--- /dev/null
+++ b/tests/topotests/munet/mutestshare.py
@@ -0,0 +1,254 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# January 28 2023, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2023, LabN Consulting, L.L.C.
+#
+"""A tiny init for namespaces in python inspired by the C program tini."""
+import argparse
+import errno
+import logging
+import os
+import shlex
+import signal
+import subprocess
+import sys
+import threading
+import time
+
+from signal import Signals as S
+
+from . import linux
+from .base import commander
+
+
+child_pid = -1
+very_verbose = False
+restore_signals = set()
+
+
+def vdebug(*args, **kwargs):
+ if very_verbose:
+ logging.debug(*args, **kwargs)
+
+
+def exit_with_status(pid, status):
+ try:
+ ec = status >> 8 if bool(status & 0xFF00) else status | 0x80
+ logging.debug("reaped our child, exiting %s", ec)
+ sys.exit(ec)
+ except ValueError:
+ vdebug("pid %s didn't actually exit", pid)
+
+
+def waitpid(tag):
+ logging.debug("%s: waitid for exiting processes", tag)
+ idobj = os.waitid(os.P_ALL, 0, os.WEXITED)
+ pid = idobj.si_pid
+ status = idobj.si_status
+ if pid == child_pid:
+ exit_with_status(pid, status)
+ else:
+ logging.debug("%s: reaped zombie pid %s with status %s", tag, pid, status)
+
+
+def new_process_group():
+ pid = os.getpid()
+ try:
+ pgid = os.getpgrp()
+ if pgid == pid:
+ logging.debug("already process group leader %s", pgid)
+ else:
+ logging.debug("creating new process group %s", pid)
+ os.setpgid(pid, 0)
+ except Exception as error:
+ logging.warning("unable to get new process group: %s", error)
+ return
+
+ # Block these in order to allow foregrounding, otherwise we'd get SIGTTOU blocked
+ signal.signal(S.SIGTTIN, signal.SIG_IGN)
+ signal.signal(S.SIGTTOU, signal.SIG_IGN)
+ fd = sys.stdin.fileno()
+ if not os.isatty(fd):
+ logging.debug("stdin not a tty no foregrounding required")
+ else:
+ try:
+ # This will error if our session no longer associated with controlling tty.
+ pgid = os.tcgetpgrp(fd)
+ if pgid == pid:
+ logging.debug("process group already in foreground %s", pgid)
+ else:
+ logging.debug("making us the foreground pgid backgrounding %s", pgid)
+ os.tcsetpgrp(fd, pid)
+ except OSError as error:
+ if error.errno == errno.ENOTTY:
+ logging.debug("session is no longer associated with controlling tty")
+ else:
+ logging.warning("unable to foreground pgid %s: %s", pid, error)
+ signal.signal(S.SIGTTIN, signal.SIG_DFL)
+ signal.signal(S.SIGTTOU, signal.SIG_DFL)
+
+
+def exec_child(exec_args):
+ # Restore signals to default handling:
+ for snum in restore_signals:
+ signal.signal(snum, signal.SIG_DFL)
+
+ # Create new process group.
+ new_process_group()
+
+ estring = shlex.join(exec_args)
+ try:
+ # and exec the process
+ logging.debug("child: executing '%s'", estring)
+ os.execvp(exec_args[0], exec_args)
+ # NOTREACHED
+ except Exception as error:
+ logging.warning("child: unable to execute '%s': %s", estring, error)
+ raise
+
+
+def is_creating_pid_namespace():
+ p1name = subprocess.check_output(
+ "readlink /proc/self/pid", stderr=subprocess.STDOUT, shell=True
+ )
+ p2name = subprocess.check_output(
+ "readlink /proc/self/pid_for_children", stderr=subprocess.STDOUT, shell=True
+ )
+ return p1name != p2name
+
+
+def restore_namespace(ppid_fd, uflags):
+ fd = ppid_fd
+ retry = 3
+ for i in range(0, retry):
+ try:
+ linux.setns(fd, uflags)
+ except OSError as error:
+ logging.warning("could not reset to old namespace fd %s: %s", fd, error)
+ if i == retry - 1:
+ raise
+ time.sleep(1)
+ os.close(fd)
+
+
+def create_thread_test():
+ def runthread(name):
+ logging.info("In thread: %s", name)
+
+ logging.info("Create thread")
+ thread = threading.Thread(target=runthread, args=(1,))
+ logging.info("Run thread")
+ thread.start()
+ logging.info("Join thread")
+ thread.join()
+
+
+def run(args):
+ del args
+ # We look for this b/c the unshare pid will share with /sibn/init
+ # nselm = "pid_for_children"
+ # nsflags.append(f"--pid={pp / nselm}")
+ # mutini now forks when created this way
+ # cmd.append("--pid")
+ # cmd.append("--fork")
+ # cmd.append("--kill-child")
+ # cmd.append("--mount-proc")
+
+ uflags = linux.CLONE_NEWPID
+ nslist = ["pid_for_children"]
+ uflags |= linux.CLONE_NEWNS
+ nslist.append("mnt")
+ uflags |= linux.CLONE_NEWNET
+ nslist.append("net")
+
+ # Before values
+ pid = os.getpid()
+ nsdict = {x: os.readlink(f"/tmp/mu-global-proc/{pid}/ns/{x}") for x in nslist}
+
+ #
+ # UNSHARE
+ #
+ create_thread_test()
+
+ ppid = os.getppid()
+ ppid_fd = linux.pidfd_open(ppid)
+ linux.unshare(uflags)
+
+ # random syscall's fail until we fork a child to establish the new pid namespace.
+ global child_pid # pylint: disable=global-statement
+ child_pid = os.fork()
+ if not child_pid:
+ logging.info("In child sleeping")
+ time.sleep(1200)
+ sys.exit(1)
+
+ # verify after values differ
+ nnsdict = {x: os.readlink(f"/tmp/mu-global-proc/{pid}/ns/{x}") for x in nslist}
+ assert not {k for k in nsdict if nsdict[k] == nnsdict[k]}
+
+ # Remount / and any future mounts below it as private
+ commander.cmd_raises("mount --make-rprivate /")
+ # Mount a new /proc in our new namespace
+ commander.cmd_raises("mount -t proc proc /proc")
+
+ #
+ # In NEW NS
+ #
+
+ cid = os.fork()
+ if not cid:
+ logging.info("In second child sleeping")
+ time.sleep(4)
+ sys.exit(1)
+ logging.info("Waiting for second child")
+ os.waitpid(cid, 0)
+
+ try:
+ create_thread_test()
+ except Exception as error:
+ print(error)
+
+ #
+ # RESTORE
+ #
+
+ logging.info("In new namespace, restoring old")
+ # Make sure we can go back, not sure since this is PID namespace, but maybe
+ restore_namespace(ppid_fd, uflags)
+
+ # verify after values the same
+ nnsdict = {x: os.readlink(f"/proc/self/ns/{x}") for x in nslist}
+ assert nsdict == nnsdict
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument(
+ "-v", dest="verbose", action="count", default=0, help="More -v's, more verbose"
+ )
+ ap.add_argument("rest", nargs=argparse.REMAINDER)
+ args = ap.parse_args()
+
+ level = logging.DEBUG if args.verbose else logging.INFO
+ if args.verbose > 1:
+ global very_verbose # pylint: disable=global-statement
+ very_verbose = True
+ logging.basicConfig(
+ level=level, format="%(asctime)s mutini: %(levelname)s: %(message)s"
+ )
+
+ status = 4
+ try:
+ run(args)
+ except KeyboardInterrupt:
+ logging.info("exiting (main), received KeyboardInterrupt in main")
+ except Exception as error:
+ logging.info("exiting (main), unexpected exception %s", error, exc_info=True)
+
+ sys.exit(status)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/topotests/munet/mutini.py b/tests/topotests/munet/mutini.py
new file mode 100755
index 0000000000..e5f9931714
--- /dev/null
+++ b/tests/topotests/munet/mutini.py
@@ -0,0 +1,432 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# January 28 2023, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2023, LabN Consulting, L.L.C.
+#
+"""A tiny init for namespaces in python inspired by the C program tini."""
+
+
+# pylint: disable=global-statement
+import argparse
+import errno
+import logging
+import os
+import re
+import shlex
+import signal
+import subprocess
+import sys
+
+from signal import Signals as S
+
+
+try:
+ from munet import linux
+except ModuleNotFoundError:
+ # We cannot use relative imports and still run this module directly as a script, and
+ # there are some use cases where we want to run this file as a script.
+ sys.path.append(os.path.dirname(os.path.realpath(__file__)))
+ import linux
+
+
+class g:
+ """Global variables for our program."""
+
+ child_pid = -1
+ orig_pid = os.getpid()
+ exit_signal = False
+ pid_status_cache = {}
+ restore_signals = set()
+ very_verbose = False
+
+
+unshare_flags = {
+ "C": linux.CLONE_NEWCGROUP,
+ "i": linux.CLONE_NEWIPC,
+ "m": linux.CLONE_NEWNS,
+ "n": linux.CLONE_NEWNET,
+ "p": linux.CLONE_NEWPID,
+ "u": linux.CLONE_NEWUTS,
+ "T": linux.CLONE_NEWTIME,
+}
+
+
+ignored_signals = {
+ S.SIGTTIN,
+ S.SIGTTOU,
+}
+abort_signals = {
+ S.SIGABRT,
+ S.SIGBUS,
+ S.SIGFPE,
+ S.SIGILL,
+ S.SIGKILL,
+ S.SIGSEGV,
+ S.SIGSTOP,
+ S.SIGSYS,
+ S.SIGTRAP,
+}
+no_prop_signals = abort_signals | ignored_signals | {S.SIGCHLD}
+
+
+def vdebug(*args, **kwargs):
+ if g.very_verbose:
+ logging.debug(*args, **kwargs)
+
+
+def get_pid_status_item(status, stat):
+ m = re.search(rf"(?:^|\n){stat}:\t(.*)(?:\n|$)", status)
+ return m.group(1).strip() if m else None
+
+
+def pget_pid_status_item(pid, stat):
+ if pid not in g.pid_status_cache:
+ with open(f"/proc/{pid}/status", "r", encoding="utf-8") as f:
+ g.pid_status_cache[pid] = f.read().strip()
+ return get_pid_status_item(g.pid_status_cache[pid], stat).strip()
+
+
+def get_pid_name(pid):
+ try:
+ return get_pid_status_item(g.pid_status_cache[pid], "Name")
+ except Exception:
+ return str(pid)
+
+
+# def init_get_child_pids():
+# """Return list of "children" pids.
+# We consider any process with a 0 parent pid to also be our child as it
+# nsentered our pid namespace from an external parent.
+# """
+# g.pid_status_cache.clear()
+# pids = (int(x) for x in os.listdir("/proc") if x.isdigit() and x != "1")
+# return (
+# x for x in pids if x == g.child_pid or pget_pid_status_item(x, "PPid") == "0"
+# )
+
+
+def exit_with_status(status):
+ if os.WIFEXITED(status):
+ ec = os.WEXITSTATUS(status)
+ elif os.WIFSIGNALED(status):
+ ec = 0x80 | os.WTERMSIG(status)
+ else:
+ ec = 255
+ logging.debug("exiting with code %s", ec)
+ sys.exit(ec)
+
+
+def waitpid(tag):
+ logging.debug("%s: waitid for exiting process", tag)
+ idobj = os.waitid(os.P_ALL, 0, os.WEXITED)
+ pid = idobj.si_pid
+ status = idobj.si_status
+
+ if pid != g.child_pid:
+ pidname = get_pid_name(pid)
+ logging.debug(
+ "%s: reaped zombie %s (%s) w/ status %s", tag, pid, pidname, status
+ )
+ return
+
+ logging.debug("reaped child with status %s", status)
+ exit_with_status(status)
+ # NOTREACHED
+
+
+def sig_trasmit(signum, _):
+ signame = signal.Signals(signum).name
+ if g.child_pid == -1:
+ # We've received a signal after setting up to be init proc
+ # but prior to fork or fork returning with child pid
+ logging.debug("received %s prior to child exec, exiting", signame)
+ sys.exit(0x80 | signum)
+
+ try:
+ os.kill(g.child_pid, signum)
+ except OSError as error:
+ if error.errno != errno.ESRCH:
+ logging.error(
+ "error forwarding signal %s to child, exiting: %s", signum, error
+ )
+ sys.exit(0x80 | signum)
+ logging.debug("child pid %s exited prior to signaling", g.child_pid)
+
+
+def sig_sigchld(signum, _):
+ assert signum == S.SIGCHLD
+ try:
+ waitpid("SIGCHLD")
+ except ChildProcessError as error:
+ logging.warning("got SIGCHLD but no pid to wait on: %s", error)
+
+
+def setup_init_signals():
+ valid = set(signal.valid_signals())
+ named = set(x.value for x in signal.Signals)
+ for snum in sorted(named):
+ if snum not in valid:
+ continue
+ if S.SIGRTMIN <= snum <= S.SIGRTMAX:
+ continue
+
+ sname = signal.Signals(snum).name
+ if snum == S.SIGCHLD:
+ vdebug("installing local handler for %s", sname)
+ signal.signal(snum, sig_sigchld)
+ g.restore_signals.add(snum)
+ elif snum in ignored_signals:
+ vdebug("installing ignore handler for %s", sname)
+ signal.signal(snum, signal.SIG_IGN)
+ g.restore_signals.add(snum)
+ elif snum in abort_signals:
+ vdebug("leaving default handler for %s", sname)
+ # signal.signal(snum, signal.SIG_DFL)
+ else:
+ vdebug("installing trasmit signal handler for %s", sname)
+ try:
+ signal.signal(snum, sig_trasmit)
+ g.restore_signals.add(snum)
+ except OSError as error:
+ logging.warning(
+ "failed installing signal handler for %s: %s", sname, error
+ )
+
+
+def new_process_group():
+ """Create and lead a new process group.
+
+ This function will create a new process group if we are not yet leading one, and
+ additionally foreground said process group in our session. This foregrounding
+ action is copied from tini, and I believe serves a purpose when serving as init
+ for a container (e.g., podman).
+ """
+ pid = os.getpid()
+ try:
+ pgid = os.getpgrp()
+ if pgid == pid:
+ logging.debug("already process group leader %s", pgid)
+ else:
+ logging.debug("creating new process group %s", pid)
+ os.setpgid(pid, 0)
+ except Exception as error:
+ logging.warning("unable to get new process group: %s", error)
+ return
+
+ # Block these in order to allow foregrounding, otherwise we'd get SIGTTOU blocked
+ signal.signal(S.SIGTTIN, signal.SIG_IGN)
+ signal.signal(S.SIGTTOU, signal.SIG_IGN)
+ fd = sys.stdin.fileno()
+ if not os.isatty(fd):
+ logging.debug("stdin not a tty no foregrounding required")
+ else:
+ try:
+ # This will error if our session no longer associated with controlling tty.
+ pgid = os.tcgetpgrp(fd)
+ if pgid == pid:
+ logging.debug("process group already in foreground %s", pgid)
+ else:
+ logging.debug("making us the foreground pgid backgrounding %s", pgid)
+ os.tcsetpgrp(fd, pid)
+ except OSError as error:
+ if error.errno == errno.ENOTTY:
+ logging.debug("session is no longer associated with controlling tty")
+ else:
+ logging.warning("unable to foreground pgid %s: %s", pid, error)
+ signal.signal(S.SIGTTIN, signal.SIG_DFL)
+ signal.signal(S.SIGTTOU, signal.SIG_DFL)
+
+
+def is_creating_pid_namespace():
+ p1name = subprocess.check_output(
+ "readlink /proc/self/pid", stderr=subprocess.STDOUT, shell=True
+ )
+ p2name = subprocess.check_output(
+ "readlink /proc/self/pid_for_children", stderr=subprocess.STDOUT, shell=True
+ )
+ return p1name != p2name
+
+
+def be_init(new_pg, exec_args):
+ #
+ # Arrange for us to be killed when our parent dies, this will subsequently also kill
+ # all procs in any PID namespace we are init for.
+ #
+ logging.debug("set us to be SIGKILLed when parent exits")
+ linux.set_parent_death_signal(signal.SIGKILL)
+
+ # If we are createing a new PID namespace for children...
+ if g.orig_pid != 1:
+ logging.debug("started as pid %s", g.orig_pid)
+ # assert is_creating_pid_namespace()
+
+ # Fork to become pid 1
+ logging.debug("forking to become pid 1")
+ child_pid = os.fork()
+ if child_pid:
+ logging.debug("in parent waiting on child pid %s to exit", child_pid)
+ status = os.wait()
+ logging.debug("got child exit status %s", status)
+ exit_with_status(status)
+ # NOTREACHED
+
+ # We must be pid 1 now.
+ logging.debug("in child as pid %s", os.getpid())
+ assert os.getpid() == 1
+
+ # We need a new /proc now.
+ logging.debug("mount new /proc")
+ linux.mount("proc", "/proc", "proc")
+
+ # If the parent exists kill us using SIGKILL
+ logging.debug("set us to be SIGKILLed when parent exits")
+ linux.set_parent_death_signal(signal.SIGKILL)
+
+ if not exec_args:
+ if not new_pg:
+ logging.debug("no exec args, no new process group")
+ # # if 0 == os.getpgid(0):
+ # status = os.setpgid(0, 1)
+ # logging.debug("os.setpgid(0, 1) == %s", status)
+ else:
+ logging.debug("no exec args, creating new process group")
+ # No exec so we are the "child".
+ new_process_group()
+
+ # Reap children as init process
+ vdebug("installing local handler for SIGCHLD")
+ signal.signal(signal.SIGCHLD, sig_sigchld)
+
+ while True:
+ logging.info("init: waiting to reap zombies")
+ linux.pause()
+ # NOTREACHED
+
+ # Set (parent) signal handlers before any fork to avoid race
+ setup_init_signals()
+
+ logging.debug("forking to execute child")
+ g.child_pid = os.fork()
+ if g.child_pid == 0:
+ # In child, restore signals to default handling:
+ for snum in g.restore_signals:
+ signal.signal(snum, signal.SIG_DFL)
+
+ # XXX is a new pg right?
+ new_process_group()
+ logging.debug("child: executing '%s'", shlex.join(exec_args))
+ os.execvp(exec_args[0], exec_args)
+ # NOTREACHED
+
+ while True:
+ logging.info("parent: waiting for child pid %s to exit", g.child_pid)
+ waitpid("parent")
+
+
+def unshare(flags):
+ """Unshare into new namespaces."""
+ uflags = 0
+ for flag in flags:
+ if flag not in unshare_flags:
+ raise ValueError(f"unknown unshare flag '{flag}'")
+ uflags |= unshare_flags[flag]
+ new_pid = bool(uflags & linux.CLONE_NEWPID)
+ new_mnt = bool(uflags & linux.CLONE_NEWNS)
+
+ logging.debug("unshareing with flags: %s", linux.clone_flag_string(uflags))
+ linux.unshare(uflags)
+
+ if new_pid and not new_mnt:
+ try:
+ # If we are not creating new mount namspace, remount /proc private
+ # so that our mount of a new /proc doesn't affect parent namespace
+ logging.debug("remount /proc recursive private")
+ linux.mount("none", "/proc", None, linux.MS_REC | linux.MS_PRIVATE)
+ except OSError as error:
+ # EINVAL is OK b/c /proc not mounted may cause an error
+ if error.errno != errno.EINVAL:
+ raise
+ if new_mnt:
+ # Remount root as recursive private.
+ logging.debug("remount / recursive private")
+ linux.mount("none", "/", None, linux.MS_REC | linux.MS_PRIVATE)
+
+ # if new_pid:
+ # logging.debug("mount new /proc")
+ # linux.mount("proc", "/proc", "proc")
+
+ return new_pid
+
+
+def main():
+ #
+ # Parse CLI args.
+ #
+
+ ap = argparse.ArgumentParser()
+ ap.add_argument(
+ "-P",
+ "--no-proc-group",
+ action="store_true",
+ help="set to inherit the process group",
+ )
+ valid_flags = "".join(unshare_flags)
+ ap.add_argument(
+ "--unshare-flags",
+ help=(
+ f"string of unshare(1) flags. Supported values from '{valid_flags}'."
+ " 'm' will remount `/` recursive private. 'p' will remount /proc"
+ " and fork, and the child will be signaled to exit on exit of parent.."
+ ),
+ )
+ ap.add_argument(
+ "-v", dest="verbose", action="count", default=0, help="more -v's, more verbose"
+ )
+ ap.add_argument("rest", nargs=argparse.REMAINDER)
+ args = ap.parse_args()
+
+ #
+ # Setup logging.
+ #
+
+ level = logging.DEBUG if args.verbose else logging.INFO
+ if args.verbose > 1:
+ g.very_verbose = True
+ logging.basicConfig(
+ level=level, format="%(asctime)s mutini: %(levelname)s: %(message)s"
+ )
+
+ #
+ # Run program
+ #
+
+ status = 5
+ try:
+ new_pid = False
+ if args.unshare_flags:
+ new_pid = unshare(args.unshare_flags)
+
+ if g.orig_pid != 1 and not new_pid:
+ # Simply hold the namespaces
+ while True:
+ logging.info("holding namespace waiting to be signaled to exit")
+ linux.pause()
+ # NOTREACHED
+
+ be_init(not args.no_proc_group, args.rest)
+ # NOTREACHED
+ logging.critical("Exited from be_init!")
+ except KeyboardInterrupt:
+ logging.info("exiting (main), received KeyboardInterrupt in main")
+ status = 0x80 | signal.SIGINT
+ except Exception as error:
+ logging.info("exiting (main), do to exception %s", error, exc_info=True)
+
+ sys.exit(status)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/topotests/munet/native.py b/tests/topotests/munet/native.py
new file mode 100644
index 0000000000..fecf709d1a
--- /dev/null
+++ b/tests/topotests/munet/native.py
@@ -0,0 +1,2941 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# October 1 2021, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2021-2022, LabN Consulting, L.L.C.
+#
+# pylint: disable=protected-access
+"""A module that defines objects for standalone use."""
+import asyncio
+import errno
+import getpass
+import ipaddress
+import logging
+import os
+import random
+import re
+import shlex
+import socket
+import subprocess
+import time
+
+from . import cli
+from .base import BaseMunet
+from .base import Bridge
+from .base import Commander
+from .base import LinuxNamespace
+from .base import MunetError
+from .base import Timeout
+from .base import _async_get_exec_path
+from .base import _get_exec_path
+from .base import cmd_error
+from .base import commander
+from .base import fsafe_name
+from .base import get_exec_path_host
+from .config import config_subst
+from .config import config_to_dict_with_key
+from .config import find_matching_net_config
+from .config import find_with_kv
+from .config import merge_kind_config
+
+
+class L3ContainerNotRunningError(MunetError):
+ """Exception if no running container exists."""
+
+
+def get_loopback_ips(c, nid):
+ if ip := c.get("ip"):
+ if ip == "auto":
+ return [ipaddress.ip_interface("10.255.0.0/32") + nid]
+ if isinstance(ip, str):
+ return [ipaddress.ip_interface(ip)]
+ return [ipaddress.ip_interface(x) for x in ip]
+ return []
+
+
+def make_ip_network(net, inc):
+ n = ipaddress.ip_network(net)
+ return ipaddress.ip_network(
+ (n.network_address + inc * n.num_addresses, n.prefixlen)
+ )
+
+
+def make_ip_interface(ia, inc):
+ ia = ipaddress.ip_interface(ia)
+ # this turns into a /32 fix this
+ ia = ia + ia.network.num_addresses * inc
+ # IPv6
+ ia = ipaddress.ip_interface(str(ia).replace("/32", "/24").replace("/128", "/64"))
+ return ia
+
+
+def get_ip_network(c, brid, ipv6=False):
+ ip = c.get("ipv6" if ipv6 else "ip")
+ if ip and str(ip) != "auto":
+ try:
+ ifip = ipaddress.ip_interface(ip)
+ if ifip.ip == ifip.network.network_address:
+ return ifip.network
+ return ifip
+ except ValueError:
+ return ipaddress.ip_network(ip)
+ if ipv6:
+ return make_ip_interface("fc00::fe/64", brid)
+ return make_ip_interface("10.0.0.254/24", brid)
+
+
+def parse_pciaddr(devaddr):
+ comp = re.match(
+ "(?:([0-9A-Fa-f]{4}):)?([0-9A-Fa-f]{2}):([0-9A-Fa-f]{2}).([0-7])", devaddr
+ ).groups()
+ if comp[0] is None:
+ comp[0] = "0000"
+ return [int(x, 16) for x in comp]
+
+
+def read_int_value(path):
+ return int(open(path, encoding="ascii").read())
+
+
+def read_str_value(path):
+ return open(path, encoding="ascii").read().strip()
+
+
+def read_sym_basename(path):
+ return os.path.basename(os.readlink(path))
+
+
+async def to_thread(func):
+ """to_thread for python < 3.9."""
+ try:
+ return await asyncio.to_thread(func)
+ except AttributeError:
+ logging.warning("Using backport to_thread")
+ return await asyncio.get_running_loop().run_in_executor(None, func)
+
+
+def convert_ranges_to_bitmask(ranges):
+ bitmask = 0
+ for r in ranges.split(","):
+ if "-" not in r:
+ bitmask |= 1 << int(r)
+ else:
+ x, y = (int(x) for x in r.split("-"))
+ for b in range(x, y + 1):
+ bitmask |= 1 << b
+ return bitmask
+
+
+class L2Bridge(Bridge):
+ """A linux bridge with no IP network address."""
+
+ def __init__(self, name=None, unet=None, logger=None, mtu=None, config=None):
+ """Create a linux Bridge."""
+ super().__init__(name=name, unet=unet, logger=logger, mtu=mtu)
+
+ self.config = config if config else {}
+
+ async def _async_delete(self):
+ self.logger.debug("%s: deleting", self)
+ await super()._async_delete()
+
+
+class L3Bridge(Bridge):
+ """A linux bridge with associated IP network address."""
+
+ def __init__(self, name=None, unet=None, logger=None, mtu=None, config=None):
+ """Create a linux Bridge."""
+ super().__init__(name=name, unet=unet, logger=logger, mtu=mtu)
+
+ self.config = config if config else {}
+
+ self.ip_interface = get_ip_network(self.config, self.id)
+ if hasattr(self.ip_interface, "network"):
+ self.ip_address = self.ip_interface.ip
+ self.ip_network = self.ip_interface.network
+ self.cmd_raises(f"ip addr add {self.ip_interface} dev {name}")
+ else:
+ self.ip_address = None
+ self.ip_network = self.ip_interface
+
+ self.logger.debug("%s: set IPv4 network address to %s", self, self.ip_interface)
+ self.cmd_raises("sysctl -w net.ipv4.ip_forward=1")
+
+ self.ip6_interface = None
+ if self.unet.ipv6_enable:
+ self.ip6_interface = get_ip_network(self.config, self.id, ipv6=True)
+ if hasattr(self.ip6_interface, "network"):
+ self.ip6_address = self.ip6_interface.ip
+ self.ip6_network = self.ip6_interface.network
+ self.cmd_raises(f"ip addr add {self.ip6_interface} dev {name}")
+ else:
+ self.ip6_address = None
+ self.ip6_network = self.ip6_interface
+
+ self.logger.debug(
+ "%s: set IPv6 network address to %s", self, self.ip_interface
+ )
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=1")
+
+ self.is_nat = self.config.get("nat", False)
+ if self.is_nat:
+ self.cmd_raises(
+ "iptables -t nat -A POSTROUTING "
+ f"-s {self.ip_network} ! -d {self.ip_network} "
+ f"! -o {self.name} -j MASQUERADE"
+ )
+
+ def get_intf_addr(self, ifname, ipv6=False):
+ # None is a valid interface, we have the same address for all interfaces
+ # just make sure they aren't asking for something we don't have.
+ if ifname is not None and ifname not in self.intfs:
+ return None
+ return self.ip6_interface if ipv6 else self.ip_interface
+
+ async def _async_delete(self):
+ self.logger.debug("%s: deleting", self)
+
+ if self.config.get("nat", False):
+ self.cmd_status(
+ "iptables -t nat -D POSTROUTING "
+ f"-s {self.ip_network} ! -d {self.ip_network} "
+ f"! -o {self.name} -j MASQUERADE"
+ )
+ await super()._async_delete()
+
+
+class NodeMixin:
+ """Node attributes and functionality."""
+
+ next_ord = 1
+
+ @classmethod
+ def _get_next_ord(cls):
+ # Do not use `cls` here b/c that makes the variable class specific
+ n = L3NodeMixin.next_ord
+ L3NodeMixin.next_ord = n + 1
+ return n
+
+ def __init__(self, *args, config=None, **kwargs):
+ """Create a Node."""
+ super().__init__(*args, **kwargs)
+
+ self.config = config if config else {}
+ config = self.config
+
+ self.id = int(config["id"]) if "id" in config else self._get_next_ord()
+
+ self.cmd_p = None
+ self.container_id = None
+ self.cleanup_called = False
+
+ # Clear and create rundir early
+ assert self.unet is not None
+ self.rundir = self.unet.rundir.joinpath(self.name)
+ commander.cmd_raises(f"rm -rf {self.rundir}")
+ commander.cmd_raises(f"mkdir -p {self.rundir}")
+
+ def _shebang_prep(self, config_key):
+ cmd = self.config.get(config_key, "").strip()
+ if not cmd:
+ return []
+
+ script_name = fsafe_name(config_key)
+
+ # shell_cmd is a union and can be boolean or string
+ shell_cmd = self.config.get("shell", "/bin/bash")
+ if not isinstance(shell_cmd, str):
+ if shell_cmd:
+ # i.e., "shell: true"
+ shell_cmd = "/bin/bash"
+ else:
+ # i.e., "shell: false"
+ shell_cmd = ""
+
+ # If we have a shell_cmd then we create a cleanup_cmds file in run_cmd
+ # and volume mounted it
+ if shell_cmd:
+ # Create cleanup cmd file
+ cmd = cmd.replace("%CONFIGDIR%", str(self.unet.config_dirname))
+ cmd = cmd.replace("%RUNDIR%", str(self.rundir))
+ cmd = cmd.replace("%NAME%", str(self.name))
+ cmd += "\n"
+
+ # Write out our cleanup cmd file at this time too.
+ cmdpath = os.path.join(self.rundir, f"{script_name}.shebang")
+ with open(cmdpath, mode="w+", encoding="utf-8") as cmdfile:
+ cmdfile.write(f"#!{shell_cmd}\n")
+ cmdfile.write(cmd)
+ cmdfile.flush()
+ commander.cmd_raises(f"chmod 755 {cmdpath}")
+
+ if self.container_id:
+ # XXX this counts on it being mounted in container, ugly
+ cmds = [f"/tmp/{script_name}.shebang"]
+ else:
+ cmds = [cmdpath]
+ else:
+ cmds = []
+ if isinstance(cmd, str):
+ cmds.extend(shlex.split(cmd))
+ else:
+ cmds.extend(cmd)
+ cmds = [
+ x.replace("%CONFIGDIR%", str(self.unet.config_dirname)) for x in cmds
+ ]
+ cmds = [x.replace("%RUNDIR%", str(self.rundir)) for x in cmds]
+ cmds = [x.replace("%NAME%", str(self.name)) for x in cmds]
+
+ return cmds
+
+ async def _async_shebang_cmd(self, config_key, warn=True):
+ cmds = self._shebang_prep(config_key)
+ if not cmds:
+ return 0
+
+ rc, o, e = await self.async_cmd_status(cmds, warn=warn)
+ if not rc and warn and (o or e):
+ self.logger.info(
+ f"async_shebang_cmd ({config_key}): %s", cmd_error(rc, o, e)
+ )
+ elif rc and warn:
+ self.logger.warning(
+ f"async_shebang_cmd ({config_key}): %s", cmd_error(rc, o, e)
+ )
+ else:
+ self.logger.debug(
+ f"async_shebang_cmd ({config_key}): %s", cmd_error(rc, o, e)
+ )
+
+ return rc
+
+ def has_run_cmd(self) -> bool:
+ return bool(self.config.get("cmd", "").strip())
+
+ async def get_proc_child_pid(self, p):
+ # commander is right for both unshare inline (our proc pidns)
+ # and non-inline (root pidns).
+
+ # This doesn't work b/c we can't get back to the root pidns
+
+ rootcmd = self.unet.rootcmd
+ pgrep = rootcmd.get_exec_path("pgrep")
+ spid = str(p.pid)
+ for _ in Timeout(4):
+ if p.returncode is not None:
+ self.logger.debug("%s: proc %s exited before getting child", self, p)
+ return None
+
+ rc, o, e = await rootcmd.async_cmd_status(
+ [pgrep, "-o", "-P", spid], warn=False
+ )
+ if rc == 0:
+ return int(o.strip())
+
+ await asyncio.sleep(0.1)
+ self.logger.debug(
+ "%s: no child of proc %s: %s", self, p, cmd_error(rc, o, e)
+ )
+ self.logger.warning("%s: timeout getting child pid of proc %s", self, p)
+ return None
+
+ async def run_cmd(self):
+ """Run the configured commands for this node."""
+ self.logger.debug(
+ "[rundir %s exists %s]", self.rundir, os.path.exists(self.rundir)
+ )
+
+ cmds = self._shebang_prep("cmd")
+ if not cmds:
+ return
+
+ stdout = open(os.path.join(self.rundir, "cmd.out"), "wb")
+ stderr = open(os.path.join(self.rundir, "cmd.err"), "wb")
+ self.cmd_pid = None
+ self.cmd_p = await self.async_popen(
+ cmds,
+ stdin=subprocess.DEVNULL,
+ stdout=stdout,
+ stderr=stderr,
+ start_new_session=True, # allows us to signal all children to exit
+ )
+
+ # If our process is actually the child of an nsenter fetch its pid.
+ if self.nsenter_fork:
+ self.cmd_pid = await self.get_proc_child_pid(self.cmd_p)
+
+ self.logger.debug(
+ "%s: async_popen %s => %s (cmd_pid %s)",
+ self,
+ cmds,
+ self.cmd_p.pid,
+ self.cmd_pid,
+ )
+
+ self.pytest_hook_run_cmd(stdout, stderr)
+
+ return self.cmd_p
+
+ async def _async_cleanup_cmd(self):
+ """Run the configured cleanup commands for this node.
+
+ This function is called by subclass' async_cleanup_cmd
+ """
+ self.cleanup_called = True
+
+ return await self._async_shebang_cmd("cleanup-cmd")
+
+ def has_cleanup_cmd(self) -> bool:
+ return bool(self.config.get("cleanup-cmd", "").strip())
+
+ async def async_cleanup_cmd(self):
+ """Run the configured cleanup commands for this node."""
+ return await self._async_cleanup_cmd()
+
+ def has_ready_cmd(self) -> bool:
+ return bool(self.config.get("ready-cmd", "").strip())
+
+ async def async_ready_cmd(self):
+ """Run the configured ready commands for this node."""
+ return not await self._async_shebang_cmd("ready-cmd", warn=False)
+
+ def cmd_completed(self, future):
+ self.logger.debug("%s: cmd completed callback", self)
+ try:
+ status = future.result()
+ self.logger.debug(
+ "%s: node cmd_p completed result: %s cmd: %s", self, status, self.cmd_p
+ )
+ self.cmd_pid = None
+ self.cmd_p = None
+ except asyncio.CancelledError:
+ # Should we stop the container if we have one?
+ self.logger.debug("%s: node cmd_p.wait() canceled", future)
+
+ def pytest_hook_run_cmd(self, stdout, stderr):
+ """Handle pytest options related to running the node cmd.
+
+ This function does things such as launch tail'ing windows
+ on the given files if requested by the user.
+
+ Args:
+ stdout: file-like object with a ``name`` attribute, or a path to a file.
+ stderr: file-like object with a ``name`` attribute, or a path to a file.
+ """
+ if not self.unet:
+ return
+
+ outopt = self.unet.cfgopt.getoption("--stdout")
+ outopt = outopt if outopt is not None else ""
+ if outopt == "all" or self.name in outopt.split(","):
+ outname = stdout.name if hasattr(stdout, "name") else stdout
+ self.run_in_window(f"tail -F {outname}", title=f"O:{self.name}")
+
+ if stderr:
+ erropt = self.unet.cfgopt.getoption("--stderr")
+ erropt = erropt if erropt is not None else ""
+ if erropt == "all" or self.name in erropt.split(","):
+ errname = stderr.name if hasattr(stderr, "name") else stderr
+ self.run_in_window(f"tail -F {errname}", title=f"E:{self.name}")
+
+ def pytest_hook_open_shell(self):
+ if not self.unet:
+ return
+
+ gdbcmd = self.config.get("gdb-cmd")
+ shellopt = self.unet.cfgopt.getoption("--gdb", "")
+ should_gdb = gdbcmd and (shellopt == "all" or self.name in shellopt.split(","))
+ use_emacs = self.unet.cfgopt.getoption("--gdb-use-emacs", False)
+
+ if should_gdb and not use_emacs:
+ cmds = self.config.get("gdb-target-cmds", [])
+ for cmd in cmds:
+ gdbcmd += f" '-ex={cmd}'"
+
+ bps = self.unet.cfgopt.getoption("--gdb-breakpoints", "").split(",")
+ for bp in bps:
+ gdbcmd += f" '-ex=b {bp}'"
+
+ cmds = self.config.get("gdb-run-cmd", [])
+ for cmd in cmds:
+ gdbcmd += f" '-ex={cmd}'"
+
+ self.run_in_window(gdbcmd)
+ elif should_gdb and use_emacs:
+ gdbcmd = gdbcmd.replace("gdb ", "gdb -i=mi ")
+ ecbin = self.get_exec_path("emacsclient")
+ # output = self.cmd_raises(
+ # [ecbin, "--eval", f"(gdb \"{gdbcmd} -ex='p 123456'\")"]
+ # )
+ _ = self.cmd_raises([ecbin, "--eval", f'(gdb "{gdbcmd}")'])
+
+ # can't figure out how to wait until symbols are loaded, until we do we just
+ # have to wait "long enough" for the symbol load to finish :/
+ # for _ in range(100):
+ # output = self.cmd_raises(
+ # [
+ # ecbin,
+ # "--eval",
+ # f"gdb-first-prompt",
+ # ]
+ # )
+ # if output == "nil\n":
+ # break
+ # time.sleep(0.25)
+
+ time.sleep(10)
+
+ cmds = self.config.get("gdb-target-cmds", [])
+ for cmd in cmds:
+ # we may want to quote quotes in the cmd string
+ self.cmd_raises(
+ [
+ ecbin,
+ "--eval",
+ f'(gud-gdb-run-command-fetch-lines "{cmd}" "*gud-gdb*")',
+ ]
+ )
+
+ bps = self.unet.cfgopt.getoption("--gdb-breakpoints", "").split(",")
+ for bp in bps:
+ cmd = f"br {bp}"
+ self.cmd_raises(
+ [
+ ecbin,
+ "--eval",
+ f'(gud-gdb-run-command-fetch-lines "{cmd}" "*gud-gdb*")',
+ ]
+ )
+
+ cmds = self.config.get("gdb-run-cmds", [])
+ for cmd in cmds:
+ # we may want to quote quotes in the cmd string
+ self.cmd_raises(
+ [
+ ecbin,
+ "--eval",
+ f'(gud-gdb-run-command-fetch-lines "{cmd}" "*gud-gdb*")',
+ ]
+ )
+ gdbcmd += f" '-ex={cmd}'"
+
+ shellopt = self.unet.cfgopt.getoption("--shell")
+ shellopt = shellopt if shellopt else ""
+ if shellopt == "all" or self.name in shellopt.split(","):
+ self.run_in_window("bash")
+
+ async def _async_delete(self):
+ self.logger.debug("%s: NodeMixin sub-class _async_delete", self)
+
+ if self.cmd_p:
+ await self.async_cleanup_proc(self.cmd_p, self.cmd_pid)
+ self.cmd_p = None
+
+ # Next call users "cleanup_cmd:"
+ try:
+ if not self.cleanup_called:
+ await self.async_cleanup_cmd()
+ except Exception as error:
+ self.logger.warning(
+ "Got an error during delete from async_cleanup_cmd: %s", error
+ )
+
+ # delete the LinuxNamespace/InterfaceMixin
+ await super()._async_delete()
+
+
+class SSHRemote(NodeMixin, Commander):
+ """SSHRemote a node representing an ssh connection to something."""
+
+ def __init__(
+ self,
+ name,
+ server,
+ port=22,
+ user=None,
+ password=None,
+ idfile=None,
+ **kwargs,
+ ):
+ super().__init__(name, **kwargs)
+
+ self.logger.debug("%s: creating", self)
+
+ # Things done in LinuxNamepsace we need to replicate here.
+ self.rundir = self.unet.rundir.joinpath(self.name)
+ self.unet.cmd_raises(f"rm -rf {self.rundir}")
+ self.unet.cmd_raises(f"mkdir -p {self.rundir}")
+
+ self.mgmt_ip = None
+ self.mgmt_ip6 = None
+
+ self.port = port
+
+ if user:
+ self.user = user
+ elif "SUDO_USER" in os.environ:
+ self.user = os.environ["SUDO_USER"]
+ else:
+ self.user = getpass.getuser()
+ self.password = password
+ self.idfile = idfile
+
+ self.server = f"{self.user}@{server}"
+
+ # Setup our base `pre-cmd` values
+ #
+ # We maybe should add environment variable transfer here in particular
+ # MUNET_NODENAME. The problem is the user has to explicitly approve
+ # of SendEnv variables.
+ self.__base_cmd = [
+ get_exec_path_host("sudo"),
+ "-E",
+ f"-u{self.user}",
+ get_exec_path_host("ssh"),
+ ]
+ if port != 22:
+ self.__base_cmd.append(f"-p{port}")
+ self.__base_cmd.append("-q")
+ self.__base_cmd.append("-oStrictHostKeyChecking=no")
+ self.__base_cmd.append("-oUserKnownHostsFile=/dev/null")
+ if self.idfile:
+ self.__base_cmd.append(f"-i{self.idfile}")
+ # Would be nice but has to be accepted by server config so not very useful.
+ # self.__base_cmd.append("-oSendVar='TEST'")
+ self.__base_cmd_pty = list(self.__base_cmd)
+ self.__base_cmd_pty.append("-t")
+ self.__base_cmd.append(self.server)
+ self.__base_cmd_pty.append(self.server)
+ # self.set_pre_cmd(pre_cmd, pre_cmd_tty)
+
+ self.logger.info("%s: created", self)
+
+ def has_ready_cmd(self) -> bool:
+ return bool(self.config.get("ready-cmd", "").strip())
+
+ def _get_pre_cmd(self, use_str, use_pty, ns_only=False, **kwargs):
+ pre_cmd = []
+ if self.unet:
+ pre_cmd = self.unet._get_pre_cmd(False, use_pty, ns_only=False, **kwargs)
+ if ns_only:
+ return pre_cmd
+
+ # XXX grab the env from kwargs and add to podman exec
+ # env = kwargs.get("env", {})
+ if use_pty:
+ pre_cmd = pre_cmd + self.__base_cmd_pty
+ else:
+ pre_cmd = pre_cmd + self.__base_cmd
+ return shlex.join(pre_cmd) if use_str else list(pre_cmd)
+
+ def _get_cmd_as_list(self, cmd):
+ """Given a list or string return a list form for execution.
+
+ If cmd is a string then [cmd] is returned, for most other
+ node types ["bash", "-c", cmd] is returned but in our case
+ ssh is the shell.
+
+ Args:
+ cmd: list or string representing the command to execute.
+ str_shell: if True and `cmd` is a string then run the
+ command using bash -c
+ Returns:
+ list of commands to execute.
+ """
+ return [cmd] if isinstance(cmd, str) else cmd
+
+
+# Would maybe like to refactor this into L3 and Node
+class L3NodeMixin(NodeMixin):
+ """A linux namespace with IP attributes."""
+
+ def __init__(self, *args, unet=None, **kwargs):
+ """Create an L3Node."""
+ # logging.warning(
+ # "L3NodeMixin: config %s unet %s kwargs %s", config, unet, kwargs
+ # )
+ super().__init__(*args, unet=unet, **kwargs)
+
+ self.mgmt_ip = None # set in parser.py
+ self.mgmt_ip6 = None # set in parser.py
+ self.host_intfs = {}
+ self.phy_intfs = {}
+ self.phycount = 0
+ self.phy_odrivers = {}
+ self.tapmacs = {}
+
+ self.intf_tc_count = 0
+
+ # super().__init__(name=name, **kwargs)
+
+ self.mount_volumes()
+
+ # -----------------------
+ # Setup node's networking
+ # -----------------------
+ if not unet.ipv6_enable:
+ # Disable IPv6
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=0")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
+ else:
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=1")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=0")
+
+ self.next_p2p_network = ipaddress.ip_network(f"10.254.{self.id}.0/31")
+ self.next_p2p_network6 = ipaddress.ip_network(f"fcff:ffff:{self.id:02x}::/127")
+
+ self.loopback_ip = None
+ self.loopback_ips = get_loopback_ips(self.config, self.id)
+ self.loopback_ip = self.loopback_ips[0] if self.loopback_ips else None
+ if self.loopback_ip:
+ self.cmd_raises_nsonly(f"ip addr add {self.loopback_ip} dev lo")
+ self.cmd_raises_nsonly("ip link set lo up")
+ for i, ip in enumerate(self.loopback_ips[1:]):
+ self.cmd_raises_nsonly(f"ip addr add {ip} dev lo:{i}")
+
+ # -------------------
+ # Setup node's rundir
+ # -------------------
+
+ # Not host path based, but we assume same
+ self.set_ns_cwd(self.rundir)
+
+ # Save the namespace pid
+ with open(os.path.join(self.rundir, "nspid"), "w", encoding="ascii") as f:
+ f.write(f"{self.pid}\n")
+
+ with open(os.path.join(self.rundir, "nspids"), "w", encoding="ascii") as f:
+ f.write(f'{" ".join([str(x) for x in self.pids])}\n')
+
+ # Create a hosts file to map our name
+ hosts_file = os.path.join(self.rundir, "hosts.txt")
+ with open(hosts_file, "w", encoding="ascii") as hf:
+ hf.write(
+ f"""127.0.0.1\tlocalhost {self.name}
+::1\tip6-localhost ip6-loopback
+fe00::0\tip6-localnet
+ff00::0\tip6-mcastprefix
+ff02::1\tip6-allnodes
+ff02::2\tip6-allrouters
+"""
+ )
+ if hasattr(self, "bind_mount"):
+ self.bind_mount(hosts_file, "/etc/hosts")
+
+ async def console(
+ self,
+ concmd,
+ prompt=r"(^|\r?\n)[^#\$]*[#\$] ",
+ is_bourne=True,
+ user=None,
+ password=None,
+ expects=None,
+ sends=None,
+ use_pty=False,
+ will_echo=False,
+ logfile_prefix="console",
+ trace=True,
+ **kwargs,
+ ):
+ """Create a REPL (read-eval-print-loop) driving a console.
+
+ Args:
+ concmd: string or list to popen with, or an already open socket
+ prompt: the REPL prompt to look for, the function returns when seen
+ is_bourne: True if the console is a bourne shell
+ user: user name to log in with
+ password: password to log in with
+ expects: a list of regex other than the prompt, the standard user, or
+ password to look for. "ogin:" or "[Pp]assword:"r.
+ sends: what to send when an element of `expects` matches. Can be the
+ empty string to send nothing.
+ use_pty: true for pty based expect, otherwise uses popen (pipes/files)
+ will_echo: bash is buggy in that it echo's to non-tty unlike any other
+ sh/ksh, set this value to true if running back
+ logfile_prefix: prefix for 3 logfiles opened to track the console i/o
+ trace: trace the send/expect sequence
+ **kwargs: kwargs passed on the _spawn.
+ """
+ lfname = os.path.join(self.rundir, f"{logfile_prefix}-log.txt")
+ logfile = open(lfname, "a+", encoding="utf-8")
+ logfile.write("-- start logging for: '{}' --\n".format(concmd))
+
+ lfname = os.path.join(self.rundir, f"{logfile_prefix}-read-log.txt")
+ logfile_read = open(lfname, "a+", encoding="utf-8")
+ logfile_read.write("-- start read logging for: '{}' --\n".format(concmd))
+
+ lfname = os.path.join(self.rundir, f"{logfile_prefix}-send-log.txt")
+ logfile_send = open(lfname, "a+", encoding="utf-8")
+ logfile_send.write("-- start send logging for: '{}' --\n".format(concmd))
+
+ expects = [] if expects is None else expects
+ sends = [] if sends is None else sends
+ if user:
+ expects.append("ogin:")
+ sends.append(user + "\n")
+ if password is not None:
+ expects.append("assword:")
+ sends.append(password + "\n")
+ repl = await self.shell_spawn(
+ concmd,
+ prompt,
+ expects=expects,
+ sends=sends,
+ use_pty=use_pty,
+ will_echo=will_echo,
+ is_bourne=is_bourne,
+ logfile=logfile,
+ logfile_read=logfile_read,
+ logfile_send=logfile_send,
+ trace=trace,
+ **kwargs,
+ )
+ return repl
+
+ async def monitor(
+ self,
+ sockpath,
+ prompt=r"\(qemu\) ",
+ ):
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.connect(sockpath)
+
+ pfx = os.path.basename(sockpath)
+
+ lfname = os.path.join(self.rundir, f"{pfx}-log.txt")
+ logfile = open(lfname, "a+", encoding="utf-8")
+ logfile.write("-- start logging for: '{}' --\n".format(sock))
+
+ lfname = os.path.join(self.rundir, f"{pfx}-read-log.txt")
+ logfile_read = open(lfname, "a+", encoding="utf-8")
+ logfile_read.write("-- start read logging for: '{}' --\n".format(sock))
+
+ p = self.spawn(sock, prompt, logfile=logfile, logfile_read=logfile_read)
+ from .base import ShellWrapper # pylint: disable=C0415
+
+ p.send("\n")
+ return ShellWrapper(p, prompt, None, will_echo=True, escape_ansi=True)
+
+ def mount_volumes(self):
+ for m in self.config.get("volumes", []):
+ if isinstance(m, str):
+ s = m.split(":", 1)
+ if len(s) == 1:
+ self.tmpfs_mount(s[0])
+ else:
+ spath = s[0]
+ if spath[0] == ".":
+ spath = os.path.abspath(
+ os.path.join(self.unet.config_dirname, spath)
+ )
+ self.bind_mount(spath, s[1])
+ continue
+ raise NotImplementedError("complex mounts for non-containers")
+
+ def get_ifname(self, netname):
+ for c in self.config["connections"]:
+ if c["to"] == netname:
+ return c["name"]
+ return None
+
+ def set_lan_addr(self, switch, cconf):
+ if ip := cconf.get("ip"):
+ ipaddr = ipaddress.ip_interface(ip)
+ assert ipaddr.version == 4
+ elif self.unet.autonumber and "ip" not in cconf:
+ self.logger.debug(
+ "%s: prefixlen of switch %s is %s",
+ self,
+ switch.name,
+ switch.ip_network.prefixlen,
+ )
+ n = switch.ip_network
+ ipaddr = ipaddress.ip_interface((n.network_address + self.id, n.prefixlen))
+ else:
+ ipaddr = None
+
+ if ip := cconf.get("ipv6"):
+ ip6addr = ipaddress.ip_interface(ip)
+ assert ipaddr.version == 6
+ elif self.unet.ipv6_enable and self.unet.autonumber and "ipv6" not in cconf:
+ self.logger.debug(
+ "%s: prefixlen of switch %s is %s",
+ self,
+ switch.name,
+ switch.ip6_network.prefixlen,
+ )
+ n = switch.ip6_network
+ ip6addr = ipaddress.ip_interface((n.network_address + self.id, n.prefixlen))
+ else:
+ ip6addr = None
+
+ dns_network = self.unet.topoconf.get("dns-network")
+ for ip in (ipaddr, ip6addr):
+ if not ip:
+ continue
+ ipcmd = "ip " if ip.version == 4 else "ip -6 "
+ if dns_network and dns_network == switch.name:
+ if ip.version == 4:
+ self.mgmt_ip = ip.ip
+ else:
+ self.mgmt_ip6 = ip.ip
+ ifname = cconf["name"]
+ self.set_intf_addr(ifname, ip)
+ self.logger.debug("%s: adding %s to lan intf %s", self, ip, ifname)
+ if not self.is_vm:
+ self.intf_ip_cmd(ifname, ipcmd + f"addr add {ip} dev {ifname}")
+ if hasattr(switch, "is_nat") and switch.is_nat:
+ swaddr = (
+ switch.ip_address if ip.version == 4 else switch.ip6_address
+ )
+ self.cmd_raises(ipcmd + f"route add default via {swaddr}")
+
+ def _set_p2p_addr(self, other, cconf, occonf, ipv6=False):
+ ipkey = "ipv6" if ipv6 else "ip"
+ ipaddr = ipaddress.ip_interface(cconf[ipkey]) if cconf.get(ipkey) else None
+ oipaddr = ipaddress.ip_interface(occonf[ipkey]) if occonf.get(ipkey) else None
+ self.logger.debug(
+ "%s: set_p2p_addr %s %s %s", self, other.name, ipaddr, oipaddr
+ )
+
+ if not ipaddr and not oipaddr:
+ if self.unet.autonumber:
+ if ipv6:
+ n = self.next_p2p_network6
+ self.next_p2p_network6 = make_ip_network(n, 1)
+ else:
+ n = self.next_p2p_network
+ self.next_p2p_network = make_ip_network(n, 1)
+
+ ipaddr = ipaddress.ip_interface(n)
+ oipaddr = ipaddress.ip_interface((ipaddr.ip + 1, n.prefixlen))
+ else:
+ return
+
+ if ipaddr:
+ ifname = cconf["name"]
+ self.set_intf_addr(ifname, ipaddr)
+ self.logger.debug("%s: adding %s to p2p intf %s", self, ipaddr, ifname)
+ if "physical" not in cconf and not self.is_vm:
+ self.intf_ip_cmd(ifname, f"ip addr add {ipaddr} dev {ifname}")
+
+ if oipaddr:
+ oifname = occonf["name"]
+ other.set_intf_addr(oifname, oipaddr)
+ self.logger.debug(
+ "%s: adding %s to other p2p intf %s", other, oipaddr, oifname
+ )
+ if "physical" not in occonf and not other.is_vm:
+ other.intf_ip_cmd(oifname, f"ip addr add {oipaddr} dev {oifname}")
+
+ def set_p2p_addr(self, other, cconf, occonf):
+ self._set_p2p_addr(other, cconf, occonf, ipv6=False)
+ if self.unet.ipv6_enable:
+ self._set_p2p_addr(other, cconf, occonf, ipv6=True)
+
+ async def add_host_intf(self, hname, lname, mtu=None):
+ if hname in self.host_intfs:
+ return
+ self.host_intfs[hname] = lname
+ self.unet.rootcmd.cmd_nostatus(f"ip link set {hname} down ")
+ self.unet.rootcmd.cmd_raises(f"ip link set {hname} netns {self.pid}")
+ self.cmd_raises(f"ip link set {hname} name {lname}")
+ if mtu:
+ self.cmd_raises(f"ip link set {lname} mtu {mtu}")
+ self.cmd_raises(f"ip link set {lname} up")
+
+ async def rem_host_intf(self, hname):
+ lname = self.host_intfs[hname]
+ self.cmd_raises(f"ip link set {lname} down")
+ self.cmd_raises(f"ip link set {lname} name {hname}")
+ self.cmd_raises(f"ip link set {hname} netns 1")
+ del self.host_intfs[hname]
+
+ async def add_phy_intf(self, devaddr, lname):
+ """Add a physical inteface (i.e. mv it to vfio-pci driver.
+
+ This is primarily useful for Qemu, but also for things like TREX or DPDK
+ """
+ if devaddr in self.phy_intfs:
+ return
+ self.phy_intfs[devaddr] = lname
+ index = len(self.phy_intfs)
+
+ _, _, off, fun = parse_pciaddr(devaddr)
+ doffset = off * 8 + fun
+
+ is_virtual = self.unet.rootcmd.path_exists(
+ f"/sys/bus/pci/devices/{devaddr}/physfn"
+ )
+ if is_virtual:
+ pfname = self.unet.rootcmd.cmd_raises(
+ f"ls -1 /sys/bus/pci/devices/{devaddr}/physfn/net"
+ ).strip()
+ pdevaddr = read_sym_basename(f"/sys/bus/pci/devices/{devaddr}/physfn")
+ _, _, poff, pfun = parse_pciaddr(pdevaddr)
+ poffset = poff * 8 + pfun
+
+ offset = read_int_value(
+ f"/sys/bus/pci/devices/{devaddr}/physfn/sriov_offset"
+ )
+ stride = read_int_value(
+ f"/sys/bus/pci/devices/{devaddr}/physfn/sriov_stride"
+ )
+ vf = (doffset - offset - poffset) // stride
+ mac = f"02:cc:cc:cc:{index:02x}:{self.id:02x}"
+ # Some devices require the parent to be up (e.g., ixbge)
+ self.unet.rootcmd.cmd_raises(f"ip link set {pfname} up")
+ self.unet.rootcmd.cmd_raises(f"ip link set {pfname} vf {vf} mac {mac}")
+ self.unet.rootcmd.cmd_status(f"ip link set {pfname} vf {vf} trust on")
+ self.tapmacs[devaddr] = mac
+
+ self.logger.info("Adding physical PCI device %s as %s", devaddr, lname)
+
+ # Get interface name and set to down if present
+ ec, ifname, _ = self.unet.rootcmd.cmd_status(
+ f"ls /sys/bus/pci/devices/{devaddr}/net/", warn=False
+ )
+ ifname = ifname.strip()
+ if not ec and ifname:
+ # XXX Should only do this is the device is up, and then likewise return it
+ # up on exit self.phy_intfs_hostname[devaddr] = ifname
+ self.logger.info(
+ "Setting physical PCI device %s named %s down", devaddr, ifname
+ )
+ self.unet.rootcmd.cmd_status(
+ f"ip link set {ifname} down 2> /dev/null || true"
+ )
+
+ # Get the current bound driver, and unbind
+ try:
+ driver = read_sym_basename(f"/sys/bus/pci/devices/{devaddr}/driver")
+ driver = driver.strip()
+ except Exception:
+ driver = ""
+ if driver:
+ if driver == "vfio-pci":
+ self.logger.info(
+ "Physical PCI device %s already bound to vfio-pci", devaddr
+ )
+ return
+ self.logger.info(
+ "Unbinding physical PCI device %s from driver %s", devaddr, driver
+ )
+ self.phy_odrivers[devaddr] = driver
+ self.unet.rootcmd.cmd_raises(
+ f"echo {devaddr} > /sys/bus/pci/drivers/{driver}/unbind"
+ )
+
+ # Add the device vendor and device id to vfio-pci in case it's the first time
+ vendor = read_str_value(f"/sys/bus/pci/devices/{devaddr}/vendor")
+ devid = read_str_value(f"/sys/bus/pci/devices/{devaddr}/device")
+ self.logger.info("Adding device IDs %s:%s to vfio-pci", vendor, devid)
+ ec, _, _ = self.unet.rootcmd.cmd_status(
+ f"echo {vendor} {devid} > /sys/bus/pci/drivers/vfio-pci/new_id", warn=False
+ )
+
+ if not self.unet.rootcmd.path_exists(f"/sys/bus/pci/driver/vfio-pci/{devaddr}"):
+ # Bind to vfio-pci if wasn't added with new_id
+ self.logger.info("Binding physical PCI device %s to vfio-pci", devaddr)
+ ec, _, _ = self.unet.rootcmd.cmd_status(
+ f"echo {devaddr} > /sys/bus/pci/drivers/vfio-pci/bind"
+ )
+
+ async def rem_phy_intf(self, devaddr):
+ """Remove a physical inteface (i.e. mv it away from vfio-pci driver.
+
+ This is primarily useful for Qemu, but also for things like TREX or DPDK
+ """
+ lname = self.phy_intfs.get(devaddr, "")
+ if lname:
+ del self.phy_intfs[devaddr]
+
+ # ifname = self.phy_intfs_hostname.get(devaddr, "")
+ # if ifname
+ # del self.phy_intfs_hostname[devaddr]
+
+ driver = self.phy_odrivers.get(devaddr, "")
+ if not driver:
+ self.logger.info(
+ "Physical PCI device %s was bound to vfio-pci on entry", devaddr
+ )
+ return
+
+ self.logger.info(
+ "Unbinding physical PCI device %s from driver vfio-pci", devaddr
+ )
+ self.unet.rootcmd.cmd_status(
+ f"echo {devaddr} > /sys/bus/pci/drivers/vfio-pci/unbind"
+ )
+
+ self.logger.info("Binding physical PCI device %s to driver %s", devaddr, driver)
+ ec, _, _ = self.unet.rootcmd.cmd_status(
+ f"echo {devaddr} > /sys/bus/pci/drivers/{driver}/bind"
+ )
+ if not ec:
+ del self.phy_odrivers[devaddr]
+
+ async def _async_delete(self):
+ self.logger.debug("%s: L3NodeMixin sub-class _async_delete", self)
+
+ # XXX do we need to run the cleanup command before these infra changes?
+
+ # remove any hostintf interfaces
+ for hname in list(self.host_intfs):
+ await self.rem_host_intf(hname)
+
+ # remove any hostintf interfaces
+ for devaddr in list(self.phy_intfs):
+ await self.rem_phy_intf(devaddr)
+
+ # delete the LinuxNamespace/InterfaceMixin
+ await super()._async_delete()
+
+
+class L3NamespaceNode(L3NodeMixin, LinuxNamespace):
+ """A namespace L3 node."""
+
+ def __init__(self, name, pid=True, **kwargs):
+ # logging.warning(
+ # "L3NamespaceNode: name %s MRO: %s kwargs %s",
+ # name,
+ # L3NamespaceNode.mro(),
+ # kwargs,
+ # )
+ super().__init__(name, pid=pid, **kwargs)
+ super().pytest_hook_open_shell()
+
+ async def _async_delete(self):
+ self.logger.debug("%s: deleting", self)
+ await super()._async_delete()
+
+
+class L3ContainerNode(L3NodeMixin, LinuxNamespace):
+ """An container (podman) based L3 node."""
+
+ def __init__(self, name, config, **kwargs):
+ """Create a Container Node."""
+ self.cont_exec_paths = {}
+ self.container_id = None
+ self.container_image = config["image"]
+ self.extra_mounts = []
+ assert self.container_image
+
+ self.cmd_p = None
+ self.__base_cmd = []
+ self.__base_cmd_pty = []
+
+ # don't we have a mutini or cat process?
+ super().__init__(
+ name=name,
+ config=config,
+ # pid=True,
+ # cgroup=True,
+ # private_mounts=["/sys/fs/cgroup:/sys/fs/cgroup"],
+ **kwargs,
+ )
+
+ @property
+ def is_container(self):
+ return True
+
+ def get_exec_path(self, binary):
+ """Return the full path to the binary executable inside the image.
+
+ `binary` :: binary name or list of binary names
+ """
+ return _get_exec_path(binary, self.cmd_status, self.cont_exec_paths)
+
+ async def async_get_exec_path(self, binary):
+ """Return the full path to the binary executable inside the image.
+
+ `binary` :: binary name or list of binary names
+ """
+ path = await _async_get_exec_path(
+ binary, self.async_cmd_status, self.cont_exec_paths
+ )
+ return path
+
+ def get_exec_path_host(self, binary):
+ """Return the full path to the binary executable on the host.
+
+ `binary` :: binary name or list of binary names
+ """
+ return get_exec_path_host(binary)
+
+ def _get_pre_cmd(self, use_str, use_pty, ns_only=False, root_level=False, **kwargs):
+ if ns_only:
+ return super()._get_pre_cmd(
+ use_str, use_pty, ns_only=True, root_level=root_level, **kwargs
+ )
+ if not self.cmd_p:
+ if self.container_id:
+ s = f"{self}: Running command in namespace b/c container exited"
+ self.logger.warning("%s", s)
+ raise L3ContainerNotRunningError(s)
+ self.logger.debug("%s: Running command in namespace b/c no container", self)
+ return super()._get_pre_cmd(
+ use_str, use_pty, ns_only=True, root_level=root_level, **kwargs
+ )
+
+ # We need to enter our namespaces when running the podman command
+ pre_cmd = super()._get_pre_cmd(
+ False, use_pty, ns_only=True, root_level=root_level, **kwargs
+ )
+
+ # XXX grab the env from kwargs and add to podman exec
+ # env = kwargs.get("env", {})
+ if use_pty:
+ pre_cmd = pre_cmd + self.__base_cmd_pty
+ else:
+ pre_cmd = pre_cmd + self.__base_cmd
+ return shlex.join(pre_cmd) if use_str else pre_cmd
+
+ def tmpfs_mount(self, inner):
+ # eventually would be nice to support live mounting
+ assert not self.container_id
+ self.logger.debug("Mounting tmpfs on %s", inner)
+ self.extra_mounts.append(f"--mount=type=tmpfs,destination={inner}")
+
+ def bind_mount(self, outer, inner):
+ # eventually would be nice to support live mounting
+ assert not self.container_id
+ # First bind the mount in the parent this allows things like /etc/hosts to work
+ # correctly when running "nsonly" commands
+ super().bind_mount(outer, inner)
+ # Then arrange for binding in the container as well.
+ self.logger.debug("Bind mounting %s on %s", outer, inner)
+ if not self.test_nsonly("-e", outer):
+ self.cmd_raises_nsonly(f"mkdir -p {outer}")
+ self.extra_mounts.append(f"--mount=type=bind,src={outer},dst={inner}")
+
+ def mount_volumes(self):
+ args = []
+ for m in self.config.get("volumes", []):
+ if isinstance(m, str):
+ s = m.split(":", 1)
+ if len(s) == 1:
+ args.append("--mount=type=tmpfs,destination=" + m)
+ else:
+ spath = s[0]
+ spath = os.path.abspath(
+ os.path.join(
+ os.path.dirname(self.unet.config["config_pathname"]), spath
+ )
+ )
+ if not self.test_nsonly("-e", spath):
+ self.cmd_raises_nsonly(f"mkdir -p {spath}")
+ args.append(f"--mount=type=bind,src={spath},dst={s[1]}")
+ continue
+
+ for m in self.config.get("mounts", []):
+ margs = ["type=" + m["type"]]
+ for k, v in m.items():
+ if k == "type":
+ continue
+ if v:
+ if k in ("src", "source"):
+ v = os.path.abspath(
+ os.path.join(
+ os.path.dirname(self.unet.config["config_pathname"]), v
+ )
+ )
+ if not self.test_nsonly("-e", v):
+ self.cmd_raises_nsonly(f"mkdir -p {v}")
+ margs.append(f"{k}={v}")
+ else:
+ margs.append(f"{k}")
+ args.append("--mount=" + ",".join(margs))
+
+ if args:
+ # Need to work on a way to mount into live container too
+ self.extra_mounts += args
+
+ def has_run_cmd(self) -> bool:
+ return True
+
+ async def run_cmd(self):
+ """Run the configured commands for this node."""
+ self.logger.debug("%s: starting container", self.name)
+ self.logger.debug(
+ "[rundir %s exists %s]", self.rundir, os.path.exists(self.rundir)
+ )
+
+ self.container_id = f"{self.name}-{os.getpid()}"
+ proc_path = self.unet.proc_path if self.unet else "/proc"
+ cmds = [
+ get_exec_path_host("podman"),
+ "run",
+ f"--name={self.container_id}",
+ # f"--net=ns:/proc/{self.pid}/ns/net",
+ f"--net=ns:{proc_path}/{self.pid}/ns/net",
+ f"--hostname={self.name}",
+ f"--add-host={self.name}:127.0.0.1",
+ # We can't use --rm here b/c podman fails on "stop".
+ # u"--rm",
+ ]
+
+ if self.config.get("init", True):
+ cmds.append("--init")
+
+ if self.config.get("privileged", False):
+ cmds.append("--privileged")
+ # If we don't do this then the host file system is remounted read-only on
+ # exit!
+ cmds.append("--systemd=false")
+ else:
+ cmds.extend(
+ [
+ # "--cap-add=SYS_ADMIN",
+ "--cap-add=NET_ADMIN",
+ "--cap-add=NET_RAW",
+ ]
+ )
+
+ # Add volumes:
+ if self.extra_mounts:
+ cmds += self.extra_mounts
+
+ # Add environment variables:
+ envdict = self.config.get("env", {})
+ if envdict is None:
+ envdict = {}
+ for k, v in envdict.items():
+ cmds.append(f"--env={k}={v}")
+
+ # Update capabilities
+ cmds += [f"--cap-add={x}" for x in self.config.get("cap-add", [])]
+ cmds += [f"--cap-drop={x}" for x in self.config.get("cap-drop", [])]
+ # cmds += [f"--expose={x.split(':')[0]}" for x in self.config.get("ports", [])]
+ cmds += [f"--publish={x}" for x in self.config.get("ports", [])]
+
+ # Add extra flags from user:
+ if "podman" in self.config:
+ for x in self.config["podman"].get("extra-args", []):
+ cmds.append(x.strip())
+
+ # shell_cmd is a union and can be boolean or string
+ shell_cmd = self.config.get("shell", "/bin/bash")
+ if not isinstance(shell_cmd, str):
+ if shell_cmd:
+ shell_cmd = "/bin/bash"
+ else:
+ shell_cmd = ""
+
+ # Create shebang files, filled later on
+ for key in ("cleanup-cmd", "ready-cmd"):
+ shebang_cmd = self.config.get(key, "").strip()
+ if shell_cmd and shebang_cmd:
+ script_name = fsafe_name(key)
+ # Will write the file contents out when the command is run
+ shebang_cmdpath = os.path.join(self.rundir, f"{script_name}.shebang")
+ await self.async_cmd_raises_nsonly(f"touch {shebang_cmdpath}")
+ await self.async_cmd_raises_nsonly(f"chmod 755 {shebang_cmdpath}")
+ cmds += [
+ # How can we override this?
+ # u'--entrypoint=""',
+ f"--volume={shebang_cmdpath}:/tmp/{script_name}.shebang",
+ ]
+
+ cmd = self.config.get("cmd", "").strip()
+
+ # See if we have a custom update for this `kind`
+ if kind := self.config.get("kind", None):
+ if kind in kind_run_cmd_update:
+ cmds, cmd = await kind_run_cmd_update[kind](self, shell_cmd, cmds, cmd)
+
+ # Create running command file
+ if shell_cmd and cmd:
+ assert isinstance(cmd, str)
+ # make cmd \n terminated for script
+ cmd = cmd.rstrip()
+ cmd = cmd.replace("%CONFIGDIR%", str(self.unet.config_dirname))
+ cmd = cmd.replace("%RUNDIR%", str(self.rundir))
+ cmd = cmd.replace("%NAME%", str(self.name))
+ cmd += "\n"
+ cmdpath = os.path.join(self.rundir, "cmd.shebang")
+ with open(cmdpath, mode="w+", encoding="utf-8") as cmdfile:
+ cmdfile.write(f"#!{shell_cmd}\n")
+ cmdfile.write(cmd)
+ cmdfile.flush()
+ self.cmd_raises_nsonly(f"chmod 755 {cmdpath}")
+ cmds += [
+ # How can we override this?
+ # u'--entrypoint=""',
+ f"--volume={cmdpath}:/tmp/cmds.shebang",
+ self.container_image,
+ "/tmp/cmds.shebang",
+ ]
+ else:
+ # `cmd` is a direct run (no shell) cmd
+ cmds.append(self.container_image)
+ if cmd:
+ if isinstance(cmd, str):
+ cmds.extend(shlex.split(cmd))
+ else:
+ cmds.extend(cmd)
+
+ cmds = [
+ x.replace("%CONFIGDIR%", str(self.unet.config_dirname)) for x in cmds
+ ]
+ cmds = [x.replace("%RUNDIR%", str(self.rundir)) for x in cmds]
+ cmds = [x.replace("%NAME%", str(self.name)) for x in cmds]
+
+ stdout = open(os.path.join(self.rundir, "cmd.out"), "wb")
+ stderr = open(os.path.join(self.rundir, "cmd.err"), "wb")
+ # Using nsonly avoids using `podman exec` to execute the cmds.
+ self.cmd_p = await self.async_popen_nsonly(
+ cmds,
+ stdin=subprocess.DEVNULL,
+ stdout=stdout,
+ stderr=stderr,
+ start_new_session=True, # keeps main tty signals away from podman
+ )
+
+ self.logger.debug("%s: async_popen => %s", self, self.cmd_p.pid)
+
+ self.pytest_hook_run_cmd(stdout, stderr)
+
+ # ---------------------------------------
+ # Now let's wait until container shows up
+ # ---------------------------------------
+ timeout = Timeout(30)
+ while self.cmd_p.returncode is None and not timeout.is_expired():
+ o = await self.async_cmd_raises_nsonly(
+ f"podman ps -q -f name={self.container_id}"
+ )
+ if o.strip():
+ break
+ elapsed = int(timeout.elapsed())
+ if elapsed <= 3:
+ await asyncio.sleep(0.1)
+ else:
+ self.logger.info("%s: run_cmd taking more than %ss", self, elapsed)
+ await asyncio.sleep(1)
+ if self.cmd_p.returncode is not None:
+ # leave self.container_id set to cause exception on use
+ self.logger.warning(
+ "%s: run_cmd exited quickly (%ss) rc: %s",
+ self,
+ timeout.elapsed(),
+ self.cmd_p.returncode,
+ )
+ elif timeout.is_expired():
+ self.logger.critical(
+ "%s: timeout (%ss) waiting for container to start",
+ self.name,
+ timeout.elapsed(),
+ )
+ assert not timeout.is_expired()
+
+ #
+ # Set our precmd for executing in the container
+ #
+ self.__base_cmd = [
+ get_exec_path_host("podman"),
+ "exec",
+ f"-eMUNET_RUNDIR={self.unet.rundir}",
+ f"-eMUNET_NODENAME={self.name}",
+ "-i",
+ ]
+ self.__base_cmd_pty = list(self.__base_cmd) # copy list to pty
+ self.__base_cmd.append(self.container_id) # end regular list
+ self.__base_cmd_pty.append("-t") # add pty flags
+ self.__base_cmd_pty.append(self.container_id) # end pty list
+ # self.set_pre_cmd(self.__base_cmd, self.__base_cmd_pty) # set both pre_cmd
+
+ self.logger.info("%s: started container", self.name)
+
+ self.pytest_hook_open_shell()
+
+ return self.cmd_p
+
+ async def async_cleanup_cmd(self):
+ """Run the configured cleanup commands for this node."""
+ self.cleanup_called = True
+
+ if "cleanup-cmd" not in self.config:
+ return
+
+ if not self.cmd_p:
+ self.logger.warning("async_cleanup_cmd: container no longer running")
+ return
+
+ return await self._async_cleanup_cmd()
+
+ def cmd_completed(self, future):
+ try:
+ log = self.logger.debug if self.deleting else self.logger.warning
+ n = future.result()
+ if self.deleting:
+ log("contianer `cmd:` result: %s", n)
+ else:
+ log(
+ "contianer `cmd:` exited early, "
+ "try adding `tail -f /dev/null` to `cmd:`, result: %s",
+ n,
+ )
+ except asyncio.CancelledError as error:
+ # Should we stop the container if we have one? or since we are canceled
+ # we know we will be deleting soon?
+ self.logger.warning(
+ "node container cmd wait() canceled: %s:%s", future, error
+ )
+ self.cmd_p = None
+
+ async def _async_delete(self):
+ self.logger.debug("%s: deleting", self)
+
+ if contid := self.container_id:
+ try:
+ if not self.cleanup_called:
+ self.logger.debug("calling user cleanup cmd")
+ await self.async_cleanup_cmd()
+ except Exception as error:
+ self.logger.warning(
+ "Got an error during delete from async_cleanup_cmd: %s", error
+ )
+
+ # Clear the container_id field we want to act like a namespace now.
+ self.container_id = None
+
+ o = ""
+ e = ""
+ if self.cmd_p:
+ self.logger.debug("podman stop on container: %s", contid)
+ if (rc := self.cmd_p.returncode) is None:
+ rc, o, e = await self.async_cmd_status_nsonly(
+ [get_exec_path_host("podman"), "stop", "--time=2", contid]
+ )
+ if rc and rc < 128:
+ self.logger.warning(
+ "%s: podman stop on cmd failed: %s",
+ self,
+ cmd_error(rc, o, e),
+ )
+ else:
+ # It's gone
+ self.cmd_p = None
+
+ # now remove the container
+ self.logger.debug("podman rm on container: %s", contid)
+ rc, o, e = await self.async_cmd_status_nsonly(
+ [get_exec_path_host("podman"), "rm", contid]
+ )
+ if rc:
+ self.logger.warning(
+ "%s: podman rm failed: %s", self, cmd_error(rc, o, e)
+ )
+ else:
+ self.logger.debug(
+ "podman removed container %s: %s", contid, cmd_error(rc, o, e)
+ )
+
+ await super()._async_delete()
+
+
+class L3QemuVM(L3NodeMixin, LinuxNamespace):
+ """An VM (qemu) based L3 node."""
+
+ def __init__(self, name, config, **kwargs):
+ """Create a Container Node."""
+ self.cont_exec_paths = {}
+ self.launch_p = None
+ self.qemu_config = config["qemu"]
+ self.extra_mounts = []
+ assert self.qemu_config
+ self.cmdrepl = None
+ self.conrepl = None
+ self.is_kvm = False
+ self.monrepl = None
+ self.tapfds = {}
+ self.cpu_thread_map = {}
+
+ self.tapnames = {}
+
+ self.use_ssh = False
+ self.__base_cmd = []
+ self.__base_cmd_pty = []
+
+ super().__init__(name=name, config=config, pid=False, **kwargs)
+
+ self.sockdir = self.rundir.joinpath("s")
+ self.cmd_raises(f"mkdir -p {self.sockdir}")
+
+ self.qemu_config = config_subst(
+ self.qemu_config,
+ name=self.name,
+ rundir=os.path.join(self.rundir, self.name),
+ configdir=self.unet.config_dirname,
+ )
+ self.ssh_keyfile = self.qemu_config.get("sshkey")
+
+ @property
+ def is_vm(self):
+ return True
+
+ def __setup_ssh(self):
+ if not self.ssh_keyfile:
+ self.logger.warning("%s: No sshkey config", self)
+ return False
+ if not self.mgmt_ip and not self.mgmt_ip6:
+ self.logger.warning("%s: No mgmt IP to ssh to", self)
+ return False
+ mgmt_ip = self.mgmt_ip if self.mgmt_ip else self.mgmt_ip6
+
+ #
+ # Since we have a keyfile shouldn't need to sudo
+ # self.user = os.environ.get("SUDO_USER", "")
+ # if not self.user:
+ # self.user = getpass.getuser()
+ # self.__base_cmd = [
+ # get_exec_path_host("sudo"),
+ # "-E",
+ # f"-u{self.user}",
+ # get_exec_path_host("ssh"),
+ # ]
+ #
+ port = 22
+ self.__base_cmd = [get_exec_path_host("ssh")]
+ if port != 22:
+ self.__base_cmd.append(f"-p{port}")
+ self.__base_cmd.append("-i")
+ self.__base_cmd.append(self.ssh_keyfile)
+ self.__base_cmd.append("-q")
+ self.__base_cmd.append("-oStrictHostKeyChecking=no")
+ self.__base_cmd.append("-oUserKnownHostsFile=/dev/null")
+ # Would be nice but has to be accepted by server config so not very useful.
+ # self.__base_cmd.append("-oSendVar='TEST'")
+ self.__base_cmd_pty = list(self.__base_cmd)
+ self.__base_cmd_pty.append("-t")
+
+ user = self.qemu_config.get("sshuser", "root")
+ self.__base_cmd.append(f"{user}@{mgmt_ip}")
+ self.__base_cmd.append("--")
+ self.__base_cmd_pty.append(f"{user}@{mgmt_ip}")
+ # self.__base_cmd_pty.append("--")
+ return True
+
+ def _get_cmd_as_list(self, cmd):
+ """Given a list or string return a list form for execution.
+
+ If cmd is a string then [cmd] is returned, for most other
+ node types ["bash", "-c", cmd] is returned but in our case
+ ssh is the shell.
+
+ Args:
+ cmd: list or string representing the command to execute.
+ str_shell: if True and `cmd` is a string then run the
+ command using bash -c
+ Returns:
+ list of commands to execute.
+ """
+ if self.use_ssh and self.launch_p:
+ return [cmd] if isinstance(cmd, str) else cmd
+ return super()._get_cmd_as_list(cmd)
+
+ def _get_pre_cmd(self, use_str, use_pty, ns_only=False, root_level=False, **kwargs):
+ if ns_only:
+ return super()._get_pre_cmd(
+ use_str, use_pty, ns_only=True, root_level=root_level, **kwargs
+ )
+
+ if not self.launch_p:
+ self.logger.debug("%s: Running command in namespace b/c no VM", self)
+ return super()._get_pre_cmd(
+ use_str, use_pty, ns_only=True, root_level=root_level, **kwargs
+ )
+
+ if not self.use_ssh:
+ self.logger.debug(
+ "%s: Running command in namespace b/c no SSH configured", self
+ )
+ return super()._get_pre_cmd(
+ use_str, use_pty, ns_only=True, root_level=root_level, **kwargs
+ )
+
+ pre_cmd = self.unet._get_pre_cmd(use_str, use_pty, ns_only=True)
+
+ # This is going to run in the process namespaces.
+ # We really want it to run in the munet namespace which will
+ # be different unless unshare_inline was used.
+ #
+ # XXX grab the env from kwargs and add to podman exec
+ # env = kwargs.get("env", {})
+ if use_pty:
+ pre_cmd = pre_cmd + self.__base_cmd_pty
+ else:
+ pre_cmd = pre_cmd + self.__base_cmd
+ return shlex.join(pre_cmd) if use_str else pre_cmd
+
+ async def moncmd(self):
+ """Uses internal REPL to send cmmand to qemu monitor and get reply."""
+
+ def tmpfs_mount(self, inner):
+ # eventually would be nice to support live mounting
+ self.logger.debug("Mounting tmpfs on %s", inner)
+ self.extra_mounts.append(("", inner, ""))
+
+ #
+ # bind_mount is actually being used to mount into the namespace
+ #
+ # def bind_mount(self, outer, inner):
+ # # eventually would be nice to support live mounting
+ # assert not self.container_id
+ # if self.test_host("-f", outer):
+ # self.logger.warning("Can't bind mount files with L3QemuVM: %s", outer)
+ # return
+ # self.logger.debug("Bind mounting %s on %s", outer, inner)
+ # if not self.test_host("-e", outer):
+ # self.cmd_raises(f"mkdir -p {outer}")
+ # self.extra_mounts.append((outer, inner, ""))
+
+ def mount_volumes(self):
+ """Mount volumes from the config."""
+ args = []
+ for m in self.config.get("volumes", []):
+ if not isinstance(m, str):
+ continue
+ s = m.split(":", 1)
+ if len(s) == 1:
+ args.append(("", s[0], ""))
+ else:
+ spath = s[0]
+ spath = os.path.abspath(
+ os.path.join(
+ os.path.dirname(self.unet.config["config_pathname"]), spath
+ )
+ )
+ if not self.test_nsonly("-e", spath):
+ self.cmd_raises_nsonly(f"mkdir -p {spath}")
+ args.append((spath, s[1], ""))
+
+ for m in self.config.get("mounts", []):
+ src = m.get("src", m.get("source", ""))
+ if src:
+ src = os.path.abspath(
+ os.path.join(
+ os.path.dirname(self.unet.config["config_pathname"]), src
+ )
+ )
+ if not self.test_nsonly("-e", src):
+ self.cmd_raises_nsonly(f"mkdir -p {src}")
+ dst = m.get("dst", m.get("destination"))
+ assert dst, "destination path required for mount"
+
+ margs = []
+ for k, v in m.items():
+ if k in ["destination", "dst", "source", "src"]:
+ continue
+ if k == "type":
+ assert v in ["bind", "tmpfs"]
+ continue
+ if not v:
+ margs.append(k)
+ else:
+ margs.append(f"{k}={v}")
+ args.append((src, dst, ",".join(margs)))
+
+ if args:
+ self.extra_mounts += args
+
+ async def run_cmd(self):
+ """Run the configured commands for this node inside VM."""
+ self.logger.debug(
+ "[rundir %s exists %s]", self.rundir, os.path.exists(self.rundir)
+ )
+
+ cmd = self.config.get("cmd", "").strip()
+ if not cmd:
+ self.logger.debug("%s: no `cmd` to run", self)
+ return None
+
+ shell_cmd = self.config.get("shell", "/bin/bash")
+ if not isinstance(shell_cmd, str):
+ if shell_cmd:
+ shell_cmd = "/bin/bash"
+ else:
+ shell_cmd = ""
+
+ if shell_cmd:
+ cmd = cmd.rstrip()
+ cmd = f"#!{shell_cmd}\n" + cmd
+ cmd = cmd.replace("%CONFIGDIR%", str(self.unet.config_dirname))
+ cmd = cmd.replace("%RUNDIR%", str(self.rundir))
+ cmd = cmd.replace("%NAME%", str(self.name))
+ cmd += "\n"
+
+ # Write a copy to the rundir
+ cmdpath = os.path.join(self.rundir, "cmd.shebang")
+ with open(cmdpath, mode="w+", encoding="utf-8") as cmdfile:
+ cmdfile.write(cmd)
+ commander.cmd_raises(f"chmod 755 {cmdpath}")
+
+ # Now write a copy inside the VM
+ self.conrepl.cmd_status("cat > /tmp/cmd.shebang << EOF\n" + cmd + "\nEOF")
+ self.conrepl.cmd_status("chmod 755 /tmp/cmd.shebang")
+ cmds = "/tmp/cmd.shebang"
+ else:
+ cmd = cmd.replace("%CONFIGDIR%", str(self.unet.config_dirname))
+ cmd = cmd.replace("%RUNDIR%", str(self.rundir))
+ cmd = cmd.replace("%NAME%", str(self.name))
+ cmds = cmd
+
+ # class future_proc:
+ # """Treat awaitable minimally as a proc."""
+ # def __init__(self, aw):
+ # self.aw = aw
+ # # XXX would be nice to have a real value here
+ # self.returncode = 0
+ # async def wait(self):
+ # if self.aw:
+ # return await self.aw
+ # return None
+
+ class now_proc:
+ """Treat awaitable minimally as a proc."""
+
+ def __init__(self, output):
+ self.output = output
+ self.returncode = 0
+
+ async def wait(self):
+ return self.output
+
+ if self.cmdrepl:
+ # self.cmd_p = future_proc(
+ # # We need our own console here b/c this is async and not returning
+ # # immediately
+ # # self.cmdrepl.run_command(cmds, timeout=120, async_=True)
+ # self.cmdrepl.run_command(cmds, timeout=120)
+ # )
+
+ # When run_command supports async_ arg we can use the above...
+ self.cmd_p = now_proc(self.cmdrepl.run_command(cmds, timeout=120))
+
+ # stdout and err both combined into logfile from the spawned repl
+ stdout = os.path.join(self.rundir, "_cmdcon-log.txt")
+ self.pytest_hook_run_cmd(stdout, None)
+ else:
+ # If we only have a console we can't run in parallel, so run to completion
+ self.cmd_p = now_proc(self.conrepl.run_command(cmds, timeout=120))
+
+ return self.cmd_p
+
+ # InterfaceMixin override
+ # We need a name unique in the shared namespace.
+ def get_ns_ifname(self, ifname):
+ return self.name + ifname
+
+ async def add_host_intf(self, hname, lname, mtu=None):
+ # L3QemuVM needs it's own add_host_intf for macvtap, We need to create the tap
+ # in the host then move that interface so that the ifindex/devfile are
+ # different.
+
+ if hname in self.host_intfs:
+ return
+
+ self.host_intfs[hname] = lname
+ index = len(self.host_intfs)
+
+ tapindex = self.unet.tapcount
+ self.unet.tapcount = self.unet.tapcount + 1
+
+ tapname = f"tap{tapindex}"
+ self.tapnames[hname] = tapname
+
+ mac = f"02:bb:bb:bb:{index:02x}:{self.id:02x}"
+ self.tapmacs[hname] = mac
+
+ self.unet.rootcmd.cmd_raises(
+ f"ip link add link {hname} name {tapname} type macvtap"
+ )
+ if mtu:
+ self.unet.rootcmd.cmd_raises(f"ip link set {tapname} mtu {mtu}")
+ self.unet.rootcmd.cmd_raises(f"ip link set {tapname} address {mac} up")
+ ifindex = self.unet.rootcmd.cmd_raises(
+ f"cat /sys/class/net/{tapname}/ifindex"
+ ).strip()
+ # self.unet.rootcmd.cmd_raises(f"ip link set {tapname} netns {self.pid}")
+
+ tapfile = f"/dev/tap{ifindex}"
+ fd = os.open(tapfile, os.O_RDWR)
+ self.tapfds[hname] = fd
+ self.logger.info(
+ "%s: Add host intf: created macvtap interface %s (%s) on %s fd %s",
+ self,
+ tapname,
+ tapfile,
+ hname,
+ fd,
+ )
+
+ async def rem_host_intf(self, hname):
+ tapname = self.tapnames[hname]
+ self.unet.rootcmd.cmd_raises(f"ip link set {tapname} down")
+ self.unet.rootcmd.cmd_raises(f"ip link delete {tapname} type macvtap")
+ del self.tapnames[hname]
+ del self.host_intfs[hname]
+
+ async def create_tap(self, index, ifname, mtu=None, driver="virtio-net-pci"):
+ # XXX we shouldn't be doign a tap on a bridge with a veth
+ # we should just be using a tap created earlier which was connected to the
+ # bridge. Except we need to handle the case of p2p qemu <-> namespace
+ #
+ ifname = self.get_ns_ifname(ifname)
+ brname = f"{self.name}br{index}"
+
+ tapindex = self.unet.tapcount
+ self.unet.tapcount += 1
+
+ mac = f"02:aa:aa:aa:{index:02x}:{self.id:02x}"
+ # nic = "tap,model=virtio-net-pci"
+ # qemu -net nic,model=virtio,addr=1a:46:0b:ca:bc:7b -net tap,fd=3 3<>/dev/tap11
+ self.cmd_raises(f"ip address flush dev {ifname}")
+ self.cmd_raises(f"ip tuntap add tap{tapindex} mode tap")
+ self.cmd_raises(f"ip link add name {brname} type bridge")
+ self.cmd_raises(f"ip link set dev {ifname} master {brname}")
+ self.cmd_raises(f"ip link set dev tap{tapindex} master {brname}")
+ if mtu:
+ self.cmd_raises(f"ip link set dev tap{tapindex} mtu {mtu}")
+ self.cmd_raises(f"ip link set dev {ifname} mtu {mtu}")
+ self.cmd_raises(f"ip link set dev tap{tapindex} up")
+ self.cmd_raises(f"ip link set dev {ifname} up")
+ self.cmd_raises(f"ip link set dev {brname} up")
+ dev = f"{driver},netdev=n{index},mac={mac}"
+ return [
+ "-netdev",
+ f"tap,id=n{index},ifname=tap{tapindex},script=no,downscript=no",
+ "-device",
+ dev,
+ ]
+
+ async def mount_mounts(self):
+ """Mount any shared directories."""
+ self.logger.info("Mounting shared directories")
+ con = self.conrepl
+ for i, m in enumerate(self.extra_mounts):
+ outer, mp, uargs = m
+ if not outer:
+ con.cmd_raises(f"mkdir -p {mp}")
+ margs = f"-o {uargs}" if uargs else ""
+ con.cmd_raises(f"mount {margs} -t tmpfs tmpfs {mp}")
+ continue
+
+ uargs = "" if uargs is None else uargs
+ margs = "trans=virtio"
+ if uargs:
+ margs += f",{uargs}"
+ self.logger.info("Mounting %s on %s with %s", outer, mp, margs)
+ con.cmd_raises(f"mkdir -p {mp}")
+ con.cmd_raises(f"mount -t 9p -o {margs} shared{i} {mp}")
+
+ async def renumber_interfaces(self):
+ """Re-number the interfaces.
+
+ After VM comes up need to renumber the interfaces now on the inside.
+ """
+ self.logger.info("Renumbering interfaces")
+ con = self.conrepl
+ con.cmd_raises("sysctl -w net.ipv4.ip_forward=1")
+ if self.unet.ipv6_enable:
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=1")
+ for ifname in sorted(self.intfs):
+ conn = find_with_kv(self.config.get("connections"), "name", ifname)
+ to = conn["to"]
+ switch = self.unet.switches.get(to)
+ mtu = conn.get("mtu")
+ if not mtu and switch:
+ mtu = switch.config.get("mtu")
+ if mtu:
+ con.cmd_raises(f"ip link set {ifname} mtu {mtu}")
+ con.cmd_raises(f"ip link set {ifname} up")
+ # In case there was some preconfig e.g., cloud-init
+ con.cmd_raises(f"ip -4 addr flush dev {ifname}")
+ sw_is_nat = switch and hasattr(switch, "is_nat") and switch.is_nat
+ if ifaddr := self.get_intf_addr(ifname, ipv6=False):
+ con.cmd_raises(f"ip addr add {ifaddr} dev {ifname}")
+ if sw_is_nat:
+ # In case there was some preconfig e.g., cloud-init
+ con.cmd_raises("ip route flush exact default")
+ con.cmd_raises(f"ip route add default via {switch.ip_address}")
+ if ifaddr := self.get_intf_addr(ifname, ipv6=True):
+ con.cmd_raises(f"ip -6 addr add {ifaddr} dev {ifname}")
+ if sw_is_nat:
+ # In case there was some preconfig e.g., cloud-init
+ con.cmd_raises("ip -6 route flush exact default")
+ con.cmd_raises(f"ip -6 route add default via {switch.ip6_address}")
+ con.cmd_raises("ip link set lo up")
+
+ if self.unet.cfgopt.getoption("--coverage"):
+ con.cmd_raises("mount -t debugfs none /sys/kernel/debug")
+
+ async def gather_coverage_data(self):
+ con = self.conrepl
+
+ gcda = "/sys/kernel/debug/gcov"
+ tmpdir = con.cmd_raises("mktemp -d").strip()
+ dest = "/gcov-data.tgz"
+ con.cmd_raises(rf"find {gcda} -type d -exec mkdir -p {tmpdir}/{{}} \;")
+ con.cmd_raises(
+ rf"find {gcda} -name '*.gcda' -exec sh -c 'cat < $0 > {tmpdir}/$0' {{}} \;"
+ )
+ con.cmd_raises(
+ rf"find {gcda} -name '*.gcno' -exec sh -c 'cp -d $0 {tmpdir}/$0' {{}} \;"
+ )
+ con.cmd_raises(rf"tar cf - -C {tmpdir} sys | gzip -c > {dest}")
+ con.cmd_raises(rf"rm -rf {tmpdir}")
+ self.logger.info("Saved coverage data in VM at %s", dest)
+ if self.use_ssh:
+ ldest = os.path.join(self.rundir, "gcov-data.tgz")
+ self.cmd_raises(["/bin/cat", dest], stdout=open(ldest, "wb"))
+ self.logger.info("Saved coverage data on host at %s", ldest)
+
+ async def _opencons(
+ self,
+ *cnames,
+ prompt=None,
+ is_bourne=True,
+ user="root",
+ password="",
+ expects=None,
+ sends=None,
+ timeout=-1,
+ ):
+ """Open consoles based on socket file names."""
+ timeo = Timeout(timeout)
+ cons = []
+ for cname in cnames:
+ sockpath = os.path.join(self.sockdir, cname)
+ connected = False
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ while self.launch_p.returncode is None and not timeo.is_expired():
+ try:
+ sock.connect(sockpath)
+ connected = True
+ break
+ except OSError as error:
+ if error.errno == errno.ENOENT:
+ self.logger.debug("waiting for console socket: %s", sockpath)
+ else:
+ self.logger.warning(
+ "can't open console socket: %s", error.strerror
+ )
+ raise
+ elapsed = int(timeo.elapsed())
+ if elapsed <= 3:
+ await asyncio.sleep(0.25)
+ else:
+ self.logger.info(
+ "%s: launch (qemu) taking more than %ss", self, elapsed
+ )
+ await asyncio.sleep(1)
+
+ if connected:
+ if prompt is None:
+ prompt = r"(^|\r\n)[^#\$]*[#\$] "
+ cons.append(
+ await self.console(
+ sock,
+ prompt=prompt,
+ is_bourne=is_bourne,
+ user=user,
+ password=password,
+ use_pty=False,
+ logfile_prefix=cname,
+ will_echo=True,
+ expects=expects,
+ sends=sends,
+ timeout=timeout,
+ trace=True,
+ )
+ )
+ elif self.launch_p.returncode is not None:
+ self.logger.warning(
+ "%s: launch (qemu) exited quickly (%ss) rc: %s",
+ self,
+ timeo.elapsed(),
+ self.launch_p.returncode,
+ )
+ raise Exception("Qemu launch exited early")
+ elif timeo.is_expired():
+ self.logger.critical(
+ "%s: timeout (%ss) waiting for qemu to start",
+ self,
+ timeo.elapsed(),
+ )
+ assert not timeo.is_expired()
+
+ return cons
+
+ async def set_cpu_affinity(self, afflist):
+ for i, aff in enumerate(afflist):
+ if not aff:
+ continue
+ # affmask = convert_ranges_to_bitmask(aff)
+ if i not in self.cpu_thread_map:
+ logging.warning("affinity %s given for missing vcpu %s", aff, i)
+ continue
+ logging.info("setting vcpu %s affinity to %s", i, aff)
+ tid = self.cpu_thread_map[i]
+ self.cmd_raises_nsonly(f"taskset -cp {aff} {tid}")
+
+ async def launch(self):
+ """Launch qemu."""
+ self.logger.info("%s: Launch Qemu", self)
+
+ qc = self.qemu_config
+ cc = qc.get("console", {})
+ bootd = "d" if "iso" in qc else "c"
+ # args = [get_exec_path_host("qemu-system-x86_64"),
+ # "-nodefaults", "-boot", bootd]
+ args = [get_exec_path_host("qemu-system-x86_64"), "-boot", bootd]
+
+ args += ["-machine", "q35"]
+
+ if qc.get("kvm"):
+ rc, _, e = await self.async_cmd_status_nsonly("ls -l /dev/kvm")
+ if rc:
+ self.logger.warning("Can't enable KVM no /dev/kvm: %s", e)
+ else:
+ # [args += ["-enable-kvm", "-cpu", "host"]
+ # uargs += ["-accel", "kvm", "-cpu", "Icelake-Server-v5"]
+ args += ["-accel", "kvm", "-cpu", "host"]
+
+ if ncpu := qc.get("ncpu"):
+ # args += ["-smp", f"sockets={ncpu}"]
+ args += ["-smp", f"cores={ncpu}"]
+ # args += ["-smp", f"{ncpu},sockets={ncpu},cores=1,threads=1"]
+
+ args.extend(["-m", str(qc.get("memory", "512M"))])
+
+ if "bios" in qc:
+ if qc["bios"] == "open-firmware":
+ args.extend(["-bios", "/usr/share/qemu/OVMF.fd"])
+ else:
+ args.extend(["-bios", qc["bios"]])
+ if "kernel" in qc:
+ args.extend(["-kernel", qc["kernel"]])
+ if "initrd" in qc:
+ args.extend(["-initrd", qc["initrd"]])
+ if "iso" in qc:
+ args.extend(["-cdrom", qc["iso"]])
+
+ # we only have append if we have a kernel
+ if "kernel" in qc:
+ args.append("-append")
+ root = qc.get("root", "/dev/ram0")
+ # Only 1 serial console the other ports (ttyS[123] hvc[01]) should have
+ # gettys in inittab
+ append = f"root={root} rw console=ttyS0"
+ if "cmdline-extra" in qc:
+ append += f" {qc['cmdline-extra']}"
+ args.append(append)
+
+ if "extra-args" in qc:
+ if isinstance(qc["extra-args"], list):
+ args.extend(qc["extra-args"])
+ else:
+ args.extend(shlex.split(qc["extra-args"]))
+
+ # Walk the list of connections in order so we attach them the same way
+ pass_fds = []
+ nnics = 0
+ pciaddr = 3
+ for index, conn in enumerate(self.config["connections"]):
+ devaddr = conn.get("physical", "")
+ hostintf = conn.get("hostintf", "")
+ if devaddr:
+ # if devaddr in self.tapmacs:
+ # mac = f",mac={self.tapmacs[devaddr]}"
+ # else:
+ # mac = ""
+ args += ["-device", f"vfio-pci,host={devaddr},addr={pciaddr}"]
+ elif hostintf:
+ fd = self.tapfds[hostintf]
+ mac = self.tapmacs[hostintf]
+ args += [
+ "-nic",
+ f"tap,model=virtio-net-pci,mac={mac},fd={fd},addr={pciaddr}",
+ ]
+ pass_fds.append(fd)
+ nnics += 1
+ elif not hostintf:
+ driver = conn.get("driver", "virtio-net-pci")
+ mtu = conn.get("mtu")
+ if not mtu and conn["to"] in self.unet.switches:
+ mtu = self.unet.switches[conn["to"]].config.get("mtu")
+ tapargs = await self.create_tap(
+ index, conn["name"], mtu=mtu, driver=driver
+ )
+ tapargs[-1] += f",addr={pciaddr}"
+ args += tapargs
+ nnics += 1
+ pciaddr += 1
+ if not nnics:
+ args += ["-nic", "none"]
+
+ dtpl = qc.get("disk-template")
+ diskpath = disk = qc.get("disk")
+ if dtpl and not disk:
+ disk = qc["disk"] = f"{self.name}-{os.path.basename(dtpl)}"
+ diskpath = os.path.join(self.rundir, disk)
+ if self.path_exists(diskpath):
+ logging.debug("Disk '%s' file exists, using.", diskpath)
+ else:
+ dtplpath = os.path.abspath(
+ os.path.join(
+ os.path.dirname(self.unet.config["config_pathname"]), dtpl
+ )
+ )
+ logging.info("Create disk '%s' from template '%s'", diskpath, dtplpath)
+ self.cmd_raises(
+ f"qemu-img create -f qcow2 -F qcow2 -b {dtplpath} {diskpath}"
+ )
+
+ if diskpath:
+ args.extend(
+ ["-drive", f"file={diskpath},if=none,id=sata-disk0,format=qcow2"]
+ )
+ args.extend(["-device", "ahci,id=ahci"])
+ args.extend(["-device", "ide-hd,bus=ahci.0,drive=sata-disk0"])
+
+ use_stdio = cc.get("stdio", True)
+ has_cmd = self.config.get("cmd")
+ use_cmdcon = has_cmd and use_stdio
+
+ #
+ # Any extra serial/console ports beyond thw first, require entries in
+ # inittab to have getty running on them, modify inittab
+ #
+ # Use -serial stdio for output only, and as the first serial console
+ # which kernel uses for printk, as it has serious issues with dropped
+ # input chars for some reason.
+ #
+ # 4 serial ports (max), we'll add extra ports using virtual consoles.
+ _sd = self.sockdir
+ if use_stdio:
+ args += ["-serial", "stdio"]
+ args += ["-serial", f"unix:{_sd}/_console,server,nowait"]
+ if use_cmdcon:
+ args += [
+ "-serial",
+ f"unix:{_sd}/_cmdcon,server,nowait",
+ ]
+ args += [
+ "-serial",
+ f"unix:{_sd}/console,server,nowait",
+ # A 2 virtual consoles - /dev/hvc[01]
+ # Requires CONFIG_HVC_DRIVER=y CONFIG_VIRTIO_CONSOLE=y
+ "-device",
+ "virtio-serial", # serial console bus
+ "-chardev",
+ f"socket,path={_sd}/vcon0,server=on,wait=off,id=vcon0",
+ "-chardev",
+ f"socket,path={_sd}/vcon1,server=on,wait=off,id=vcon1",
+ "-device",
+ "virtconsole,chardev=vcon0",
+ "-device",
+ "virtconsole,chardev=vcon1",
+ # 2 monitors
+ "-monitor",
+ f"unix:{_sd}/_monitor,server,nowait",
+ "-monitor",
+ f"unix:{_sd}/monitor,server,nowait",
+ "-gdb",
+ f"unix:{_sd}/gdbserver,server,nowait",
+ ]
+
+ for i, m in enumerate(self.extra_mounts):
+ args += [
+ "-virtfs",
+ f"local,path={m[0]},mount_tag=shared{i},security_model=passthrough",
+ ]
+
+ args += ["-nographic"]
+
+ #
+ # Launch Qemu
+ #
+
+ stdout = open(os.path.join(self.rundir, "qemu.out"), "wb")
+ stderr = open(os.path.join(self.rundir, "qemu.err"), "wb")
+ self.launch_p = await self.async_popen(
+ args,
+ stdin=subprocess.DEVNULL,
+ stdout=stdout,
+ stderr=stderr,
+ pass_fds=pass_fds,
+ # We don't need this here b/c we are only ever running qemu and that's all
+ # we need to kill for cleanup
+ # XXX reconcile this
+ start_new_session=True, # allows us to signal all children to exit
+ )
+
+ self.pytest_hook_run_cmd(stdout, stderr)
+
+ # We've passed these on, so don't need these open here anymore.
+ for fd in pass_fds:
+ os.close(fd)
+
+ self.logger.debug("%s: async_popen => %s", self, self.launch_p.pid)
+
+ confiles = ["_console"]
+ if use_cmdcon:
+ confiles.append("_cmdcon")
+
+ #
+ # Connect to the console socket, retrying
+ #
+ prompt = cc.get("prompt")
+ cons = await self._opencons(
+ *confiles,
+ prompt=prompt,
+ is_bourne=not bool(prompt),
+ user=cc.get("user", "root"),
+ password=cc.get("password", ""),
+ expects=cc.get("expects"),
+ sends=cc.get("sends"),
+ timeout=int(cc.get("timeout", 60)),
+ )
+ self.conrepl = cons[0]
+ if use_cmdcon:
+ self.cmdrepl = cons[1]
+ self.monrepl = await self.monitor(os.path.join(self.sockdir, "_monitor"))
+
+ # the monitor output has super annoying ANSI escapes in it
+
+ output = self.monrepl.cmd_nostatus("info status")
+ self.logger.info("VM status: %s", output)
+
+ output = self.monrepl.cmd_nostatus("info kvm")
+ self.logger.info("KVM status: %s", output)
+
+ #
+ # Set thread affinity
+ #
+ output = self.monrepl.cmd_nostatus("info cpus")
+ matches = re.findall(r"CPU #(\d+): *thread_id=(\d+)", output)
+ self.cpu_thread_map = {int(k): int(v) for k, v in matches}
+ if cpuaff := self.qemu_config.get("cpu-affinity"):
+ await self.set_cpu_affinity(cpuaff)
+
+ self.is_kvm = "disabled" not in output
+
+ if qc.get("unix-os", True):
+ await self.renumber_interfaces()
+
+ if self.extra_mounts:
+ await self.mount_mounts()
+
+ self.use_ssh = bool(self.ssh_keyfile)
+ if self.use_ssh:
+ self.use_ssh = self.__setup_ssh()
+
+ self.pytest_hook_open_shell()
+
+ return self.launch_p
+
+ def launch_completed(self, future):
+ self.logger.debug("%s: launch (qemu) completed called", self)
+ self.use_ssh = False
+ try:
+ n = future.result()
+ self.logger.debug("%s: node launch (qemu) completed result: %s", self, n)
+ except asyncio.CancelledError as error:
+ self.logger.debug(
+ "%s: node launch (qemu) cmd wait() canceled: %s", future, error
+ )
+
+ async def cleanup_qemu(self):
+ """Launch qemu."""
+ if self.launch_p:
+ await self.async_cleanup_proc(self.launch_p)
+
+ async def async_cleanup_cmd(self):
+ """Run the configured cleanup commands for this node."""
+ self.cleanup_called = True
+
+ if "cleanup-cmd" not in self.config:
+ return
+
+ if not self.launch_p:
+ self.logger.warning("async_cleanup_cmd: qemu no longer running")
+ return
+
+ raise NotImplementedError("Needs to be like run_cmd")
+ # return await self._async_cleanup_cmd()
+
+ async def _async_delete(self):
+ self.logger.debug("%s: deleting", self)
+
+ # Need to cleanup early b/c it is running on the VM
+ if self.cmd_p:
+ await self.async_cleanup_proc(self.cmd_p)
+ self.cmd_p = None
+
+ try:
+ # Need to cleanup early b/c it is running on the VM
+ if not self.cleanup_called:
+ await self.async_cleanup_cmd()
+ except Exception as error:
+ self.logger.warning(
+ "Got an error during delete from async_cleanup_cmd: %s", error
+ )
+
+ try:
+ if not self.launch_p:
+ self.logger.warning("async_delete: qemu is not running")
+ else:
+ await self.cleanup_qemu()
+ except Exception as error:
+ self.logger.warning("%s: failued to cleanup qemu process: %s", self, error)
+
+ await super()._async_delete()
+
+
+class Munet(BaseMunet):
+ """Munet."""
+
+ def __init__(
+ self,
+ rundir=None,
+ config=None,
+ pid=True,
+ logger=None,
+ **kwargs,
+ ):
+ # logging.warning("Munet")
+
+ if not rundir:
+ rundir = "/tmp/munet"
+
+ if logger is None:
+ logger = logging.getLogger("munet.unet")
+
+ super().__init__("munet", pid=pid, rundir=rundir, logger=logger, **kwargs)
+
+ self.built = False
+ self.tapcount = 0
+
+ self.cmd_raises(f"mkdir -p {self.rundir} && chmod 755 {self.rundir}")
+ self.set_ns_cwd(self.rundir)
+
+ if not config:
+ config = {}
+ self.config = config
+ if "config_pathname" in config:
+ self.config_pathname = os.path.realpath(config["config_pathname"])
+ self.config_dirname = os.path.dirname(self.config_pathname)
+ else:
+ self.config_pathname = ""
+ self.config_dirname = ""
+
+ # Done in BaseMunet now
+ # # We need some way to actually get back to the root namespace
+ # if not self.isolated:
+ # self.rootcmd = commander
+ # else:
+ # spid = str(pid)
+ # nsflags = (f"--mount={self.proc_path / spid / 'ns/mnt'}",
+ # f"--net={self.proc_path / spid / 'ns/net'}",
+ # f"--uts={self.proc_path / spid / 'ns/uts'}",
+ # f"--ipc={self.proc_path / spid / 'ns/ipc'}",
+ # f"--cgroup={self.proc_path / spid / 'ns/cgroup'}",
+ # f"--pid={self.proc_path / spid / 'ns/net'}",
+ # self.rootcmd = SharedNamespace("host", pid=1, nsflags=nsflags)
+
+ # Save the namespace pid
+ with open(os.path.join(self.rundir, "nspid"), "w", encoding="ascii") as f:
+ f.write(f"{self.pid}\n")
+
+ with open(os.path.join(self.rundir, "nspids"), "w", encoding="ascii") as f:
+ f.write(f'{" ".join([str(x) for x in self.pids])}\n')
+
+ hosts_file = os.path.join(self.rundir, "hosts.txt")
+ with open(hosts_file, "w", encoding="ascii") as hf:
+ hf.write(
+ f"""127.0.0.1\tlocalhost {self.name}
+::1\tip6-localhost ip6-loopback
+fe00::0\tip6-localnet
+ff00::0\tip6-mcastprefix
+ff02::1\tip6-allnodes
+ff02::2\tip6-allrouters
+"""
+ )
+ self.bind_mount(hosts_file, "/etc/hosts")
+
+ # Common CLI commands for any topology
+ cdict = {
+ "commands": [
+ {
+ "name": "pcap",
+ "format": "pcap NETWORK",
+ "help": (
+ "capture packets from NETWORK into file capture-NETWORK.pcap"
+ " the command is run within a new window which also shows"
+ " packet summaries. NETWORK can also be an interface specified"
+ " as HOST:INTF. To capture inside the host namespace."
+ ),
+ "exec": "tshark -s 9200 -i {0} -P -w capture-{0}.pcap",
+ "top-level": True,
+ "new-window": {"background": True},
+ },
+ {
+ "name": "nsterm",
+ "format": "nsterm HOST [HOST ...]",
+ "help": (
+ "open terminal[s] in the namespace only"
+ " (outside containers or VM), * for all"
+ ),
+ "exec": "bash",
+ "new-window": {"ns_only": True},
+ },
+ {
+ "name": "term",
+ "format": "term HOST [HOST ...]",
+ "help": "open terminal[s] (TMUX or XTerm) on HOST[S], * for all",
+ "exec": "bash",
+ "new-window": True,
+ },
+ {
+ "name": "xterm",
+ "format": "xterm HOST [HOST ...]",
+ "help": "open XTerm[s] on HOST[S], * for all",
+ "exec": "bash",
+ "new-window": {
+ "forcex": True,
+ },
+ },
+ {
+ "name": "sh",
+ "format": "[HOST ...] sh <SHELL-COMMAND>",
+ "help": "execute <SHELL-COMMAND> on hosts",
+ "exec": "{}",
+ },
+ {
+ "name": "shi",
+ "format": "[HOST ...] shi <INTERACTIVE-COMMAND>",
+ "help": "execute <INTERACTIVE-COMMAND> on HOST[s]",
+ "exec": "{}",
+ "interactive": True,
+ },
+ {
+ "name": "stdout",
+ "exec": (
+ "[ -e %RUNDIR%/qemu.out ] && tail -F %RUNDIR%/qemu.out "
+ "|| tail -F %RUNDIR%/cmd.out"
+ ),
+ "format": "stdout HOST [HOST ...]",
+ "help": "tail -f on the stdout of the qemu/cmd for this node",
+ "new-window": True,
+ },
+ {
+ "name": "stderr",
+ "exec": (
+ "[ -e %RUNDIR%/qemu.err ] && tail -F %RUNDIR%/qemu.err "
+ "|| tail -F %RUNDIR%/cmd.err"
+ ),
+ "format": "stderr HOST [HOST ...]",
+ "help": "tail -f on the stdout of the qemu/cmd for this node",
+ "new-window": True,
+ },
+ ]
+ }
+
+ cli.add_cli_config(self, cdict)
+
+ if "cli" in config:
+ cli.add_cli_config(self, config["cli"])
+
+ if "topology" not in self.config:
+ self.config["topology"] = {}
+
+ self.topoconf = self.config["topology"]
+ self.ipv6_enable = self.topoconf.get("ipv6-enable", False)
+
+ if self.isolated:
+ if not self.ipv6_enable:
+ # Disable IPv6
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=0")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=1")
+ else:
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.autoconf=1")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.disable_ipv6=0")
+
+ # we really need overlay, but overlay-layers (used by overlay-images)
+ # counts on things being present in overlay so this temp stuff doesn't work.
+ # if self.isolated:
+ # # Let's hide podman details
+ # self.tmpfs_mount("/var/lib/containers/storage/overlay-containers")
+
+ shellopt = self.cfgopt.getoption("--shell")
+ shellopt = shellopt if shellopt else ""
+ if shellopt == "all" or "." in shellopt.split(","):
+ self.run_in_window("bash")
+
+ def __del__(self):
+ """Catch case of build object but not async_deleted."""
+ if hasattr(self, "built"):
+ if not self.deleting:
+ logging.critical(
+ "Munet object deleted without calling `async_delete` for cleanup."
+ )
+ s = super()
+ if hasattr(s, "__del__"):
+ s.__del__(self)
+
+ async def _async_build(self, logger=None):
+ """Build the topology based on config."""
+ if self.built:
+ self.logger.warning("%s: is already built", self)
+ return
+
+ self.built = True
+
+ # Allow for all networks to be auto-numbered
+ topoconf = self.topoconf
+ autonumber = self.autonumber
+ ipv6_enable = self.ipv6_enable
+
+ # ---------------------------------------------
+ # Merge Kinds and perform variable substitution
+ # ---------------------------------------------
+
+ kinds = self.config.get("kinds", {})
+
+ for name, conf in config_to_dict_with_key(topoconf, "networks", "name").items():
+ if kind := conf.get("kind"):
+ if kconf := kinds[kind]:
+ conf = merge_kind_config(kconf, conf)
+ conf = config_subst(
+ conf, name=name, rundir=self.rundir, configdir=self.config_dirname
+ )
+ if "ip" not in conf and autonumber:
+ conf["ip"] = "auto"
+ if "ipv6" not in conf and autonumber and ipv6_enable:
+ conf["ipv6"] = "auto"
+ topoconf["networks"][name] = conf
+ self.add_network(name, conf, logger=logger)
+
+ for name, conf in config_to_dict_with_key(topoconf, "nodes", "name").items():
+ if kind := conf.get("kind"):
+ if kconf := kinds[kind]:
+ conf = merge_kind_config(kconf, conf)
+
+ config_to_dict_with_key(
+ conf, "env", "name"
+ ) # convert list of env objects to dict
+
+ conf = config_subst(
+ conf,
+ name=name,
+ rundir=os.path.join(self.rundir, name),
+ configdir=self.config_dirname,
+ )
+ topoconf["nodes"][name] = conf
+ self.add_l3_node(name, conf, logger=logger)
+
+ # ------------------
+ # Create connections
+ # ------------------
+
+ # Go through all connections and name them so they are sane to the user
+ # otherwise when we do p2p links the names/ords skip around based oddly
+ for name, node in self.hosts.items():
+ nconf = node.config
+ if "connections" not in nconf:
+ continue
+ nconns = []
+ for cconf in nconf["connections"]:
+ # Replace string only with a dictionary
+ if isinstance(cconf, str):
+ splitconf = cconf.split(":", 1)
+ cconf = {"to": splitconf[0]}
+ if len(splitconf) == 2:
+ cconf["name"] = splitconf[1]
+ # Allocate a name if not already assigned
+ if "name" not in cconf:
+ cconf["name"] = node.get_next_intf_name()
+ nconns.append(cconf)
+ nconf["connections"] = nconns
+
+ for name, node in self.hosts.items():
+ nconf = node.config
+ if "connections" not in nconf:
+ continue
+ for cconf in nconf["connections"]:
+ # Eventually can add support for unconnected intf here.
+ if "to" not in cconf:
+ continue
+ to = cconf["to"]
+ if to in self.switches:
+ switch = self.switches[to]
+ swconf = find_matching_net_config(name, cconf, switch.config)
+ await self.add_native_link(switch, node, swconf, cconf)
+ elif cconf["name"] not in node.intfs:
+ # Only add the p2p interface if not already there.
+ other = self.hosts[to]
+ oconf = find_matching_net_config(name, cconf, other.config)
+ await self.add_native_link(node, other, cconf, oconf)
+
+ @property
+ def autonumber(self):
+ return self.topoconf.get("networks-autonumber", False)
+
+ @autonumber.setter
+ def autonumber(self, value):
+ self.topoconf["networks-autonumber"] = bool(value)
+
+ async def add_native_link(self, node1, node2, c1=None, c2=None):
+ """Add a link between switch and node or 2 nodes."""
+ isp2p = False
+
+ c1 = {} if c1 is None else c1
+ c2 = {} if c2 is None else c2
+
+ if node1.name in self.switches:
+ assert node2.name in self.hosts
+ elif node2.name in self.switches:
+ assert node1.name in self.hosts
+ node1, node2 = node2, node1
+ c1, c2 = c2, c1
+ else:
+ # p2p link
+ assert node1.name in self.hosts
+ assert node1.name in self.hosts
+ isp2p = True
+
+ if "name" not in c1:
+ c1["name"] = node1.get_next_intf_name()
+ if1 = c1["name"]
+
+ if "name" not in c2:
+ c2["name"] = node2.get_next_intf_name()
+ if2 = c2["name"]
+
+ do_add_link = True
+ for n, c in ((node1, c1), (node2, c2)):
+ if "hostintf" in c:
+ await n.add_host_intf(c["hostintf"], c["name"], mtu=c.get("mtu"))
+ do_add_link = False
+ elif "physical" in c:
+ await n.add_phy_intf(c["physical"], c["name"])
+ do_add_link = False
+ if do_add_link:
+ assert "hostintf" not in c1
+ assert "hostintf" not in c2
+ assert "physical" not in c1
+ assert "physical" not in c2
+
+ if isp2p:
+ mtu1 = c1.get("mtu")
+ mtu2 = c2.get("mtu")
+ mtu = mtu1 if mtu1 else mtu2
+ if mtu1 and mtu2 and mtu1 != mtu2:
+ self.logger.error("mtus differ for add_link %s != %s", mtu1, mtu2)
+ else:
+ mtu = c2.get("mtu")
+
+ super().add_link(node1, node2, if1, if2, mtu=mtu)
+
+ if isp2p:
+ node1.set_p2p_addr(node2, c1, c2)
+ else:
+ node2.set_lan_addr(node1, c2)
+
+ if "physical" not in c1 and not node1.is_vm:
+ node1.set_intf_constraints(if1, **c1)
+ if "physical" not in c2 and not node2.is_vm:
+ node2.set_intf_constraints(if2, **c2)
+
+ def add_l3_node(self, name, config=None, **kwargs):
+ """Add a node to munet."""
+ if config and config.get("image"):
+ cls = L3ContainerNode
+ elif config and config.get("qemu"):
+ cls = L3QemuVM
+ elif config and config.get("server"):
+ cls = SSHRemote
+ kwargs["server"] = config["server"]
+ kwargs["port"] = int(config.get("server-port", 22))
+ if "ssh-identity-file" in config:
+ kwargs["idfile"] = config.get("ssh-identity-file")
+ if "ssh-user" in config:
+ kwargs["user"] = config.get("ssh-user")
+ if "ssh-password" in config:
+ kwargs["password"] = config.get("ssh-password")
+ else:
+ cls = L3NamespaceNode
+ return super().add_host(name, cls=cls, config=config, **kwargs)
+
+ def add_network(self, name, config=None, **kwargs):
+ """Add a l2 or l3 switch to munet."""
+ if config is None:
+ config = {}
+
+ cls = L3Bridge if config.get("ip") else L2Bridge
+ mtu = kwargs.get("mtu", config.get("mtu"))
+ return super().add_switch(name, cls=cls, config=config, mtu=mtu, **kwargs)
+
+ async def run(self):
+ tasks = []
+
+ hosts = self.hosts.values()
+ launch_nodes = [x for x in hosts if hasattr(x, "launch")]
+ launch_nodes = [x for x in launch_nodes if x.config.get("qemu")]
+ run_nodes = [x for x in hosts if hasattr(x, "has_run_cmd") and x.has_run_cmd()]
+ ready_nodes = [
+ x for x in hosts if hasattr(x, "has_ready_cmd") and x.has_ready_cmd()
+ ]
+
+ pcapopt = self.cfgopt.getoption("--pcap")
+ pcapopt = pcapopt if pcapopt else ""
+ if pcapopt == "all":
+ pcapopt = self.switches.keys()
+ if pcapopt:
+ for pcap in pcapopt.split(","):
+ if ":" in pcap:
+ host, intf = pcap.split(":")
+ pcap = f"{host}-{intf}"
+ host = self.hosts[host]
+ else:
+ host = self
+ intf = pcap
+ host.run_in_window(
+ f"tshark -s 9200 -i {intf} -P -w capture-{pcap}.pcap",
+ background=True,
+ title=f"cap:{pcap}",
+ )
+
+ if launch_nodes:
+ # would like a info when verbose here.
+ logging.debug("Launching nodes")
+ await asyncio.gather(*[x.launch() for x in launch_nodes])
+
+ # Watch for launched processes to exit
+ for node in launch_nodes:
+ task = asyncio.create_task(
+ node.launch_p.wait(), name=f"Node-{node.name}-launch"
+ )
+ task.add_done_callback(node.launch_completed)
+ tasks.append(task)
+
+ if run_nodes:
+ # would like a info when verbose here.
+ logging.debug("Running `cmd` on nodes")
+ await asyncio.gather(*[x.run_cmd() for x in run_nodes])
+
+ # Watch for run_cmd processes to exit
+ for node in run_nodes:
+ task = asyncio.create_task(node.cmd_p.wait(), name=f"Node-{node.name}-cmd")
+ task.add_done_callback(node.cmd_completed)
+ tasks.append(task)
+
+ # Wait for nodes to be ready
+ if ready_nodes:
+
+ async def wait_until_ready(x):
+ while not await x.async_ready_cmd():
+ logging.debug("Waiting for ready on: %s", x)
+ await asyncio.sleep(0.25)
+ logging.debug("%s is ready!", x)
+
+ logging.debug("Waiting for ready on nodes: %s", ready_nodes)
+ _, pending = await asyncio.wait(
+ [wait_until_ready(x) for x in ready_nodes], timeout=30
+ )
+ if pending:
+ logging.warning("Timeout waiting for ready: %s", pending)
+ for nr in pending:
+ nr.cancel()
+ raise asyncio.TimeoutError()
+ logging.debug("All nodes ready")
+
+ return tasks
+
+ async def _async_delete(self):
+ from .testing.util import async_pause_test # pylint: disable=C0415
+
+ self.logger.debug("%s: deleting.", self)
+
+ if self.cfgopt.getoption("--coverage"):
+ nodes = (
+ x for x in self.hosts.values() if hasattr(x, "gather_coverage_data")
+ )
+ try:
+ await asyncio.gather(*(x.gather_coverage_data() for x in nodes))
+ except Exception as error:
+ logging.warning("Error gathering coverage data: %s", error)
+
+ pause = bool(self.cfgopt.getoption("--pause-at-end"))
+ pause = pause or bool(self.cfgopt.getoption("--pause"))
+ if pause:
+ try:
+ await async_pause_test("Before MUNET delete")
+ except KeyboardInterrupt:
+ print("^C...continuing")
+ except Exception as error:
+ self.logger.error("\n...continuing after error: %s", error)
+
+ # XXX should we cancel launch and run tasks?
+
+ try:
+ await super()._async_delete()
+ except Exception as error:
+ self.logger.error("Error cleaning up: %s", error, exc_info=True)
+ raise
+
+
+async def run_cmd_update_ceos(node, shell_cmd, cmds, cmd):
+ cmd = cmd.strip()
+ if shell_cmd or cmd != "/sbin/init":
+ return cmds, cmd
+
+ #
+ # Add flash dir and mount it
+ #
+ flashdir = os.path.join(node.rundir, "flash")
+ node.cmd_raises_nsonly(f"mkdir -p {flashdir} && chmod 775 {flashdir}")
+ cmds += [f"--volume={flashdir}:/mnt/flash"]
+
+ #
+ # Startup config (if not present already)
+ #
+ if startup_config := node.config.get("startup-config", None):
+ dest = os.path.join(flashdir, "startup-config")
+ if os.path.exists(dest):
+ node.logger.info("Skipping copy of startup-config, already present")
+ else:
+ source = os.path.join(node.unet.config_dirname, startup_config)
+ node.cmd_raises_nsonly(f"cp {source} {dest} && chmod 664 {dest}")
+
+ #
+ # system mac address (if not present already
+ #
+ dest = os.path.join(flashdir, "system_mac_address")
+ if os.path.exists(dest):
+ node.logger.info("Skipping system-mac generation, already present")
+ else:
+ random_arista_mac = "00:1c:73:%02x:%02x:%02x" % (
+ random.randint(0, 255),
+ random.randint(0, 255),
+ random.randint(0, 255),
+ )
+ system_mac = node.config.get("system-mac", random_arista_mac)
+ with open(dest, "w", encoding="ascii") as f:
+ f.write(system_mac + "\n")
+ node.cmd_raises_nsonly(f"chmod 664 {dest}")
+
+ args = []
+
+ # Pass special args for the environment variables
+ if "env" in node.config:
+ args += [f"systemd.setenv={k}={v}" for k, v in node.config["env"].items()]
+
+ return cmds, [cmd] + args
+
+
+# XXX this is only used by the container code
+kind_run_cmd_update = {"ceos": run_cmd_update_ceos}
diff --git a/tests/topotests/munet/parser.py b/tests/topotests/munet/parser.py
new file mode 100644
index 0000000000..4fc0c75a60
--- /dev/null
+++ b/tests/topotests/munet/parser.py
@@ -0,0 +1,374 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# September 30 2021, Christian Hopps <chopps@labn.net>
+#
+# Copyright 2021, LabN Consulting, L.L.C.
+#
+"""A module that implements the standalone parser."""
+import asyncio
+import importlib.resources
+import json
+import logging
+import logging.config
+import os
+import subprocess
+import sys
+import tempfile
+
+from pathlib import Path
+
+
+try:
+ import jsonschema # pylint: disable=C0415
+ import jsonschema.validators # pylint: disable=C0415
+
+ from jsonschema.exceptions import ValidationError # pylint: disable=C0415
+except ImportError:
+ jsonschema = None
+
+from .config import list_to_dict_with_key
+from .native import Munet
+
+
+def get_schema():
+ if get_schema.schema is None:
+ with importlib.resources.path("munet", "munet-schema.json") as datapath:
+ search = [str(datapath.parent)]
+ get_schema.schema = get_config(basename="munet-schema", search=search)
+ return get_schema.schema
+
+
+get_schema.schema = None
+
+project_root_contains = [
+ ".git",
+ "pyproject.toml",
+ "tox.ini",
+ "setup.cfg",
+ "setup.py",
+ "pytest.ini",
+ ".projectile",
+]
+
+
+def is_project_root(path: Path) -> bool:
+
+ for contains in project_root_contains:
+ if path.joinpath(contains).exists():
+ return True
+ return False
+
+
+def find_project_root(config_path: Path, project_root=None):
+ if project_root is not None:
+ project_root = Path(project_root)
+ if project_root in config_path.parents:
+ return project_root
+ logging.warning(
+ "project_root %s is not a common ancestor of config file %s",
+ project_root,
+ config_path,
+ )
+ return config_path.parent
+ for ppath in config_path.parents:
+ if is_project_root(ppath):
+ return ppath
+ return config_path.parent
+
+
+def get_config(pathname=None, basename="munet", search=None, logf=logging.debug):
+
+ cwd = os.getcwd()
+
+ if not search:
+ search = [cwd]
+ elif isinstance(search, (str, Path)):
+ search = [search]
+
+ if pathname:
+ pathname = os.path.join(cwd, pathname)
+ if not os.path.exists(pathname):
+ raise FileNotFoundError(pathname)
+ else:
+ for d in search:
+ logf("%s", f'searching in "{d}" for "{basename}".{{yaml, toml, json}}')
+ for ext in ("yaml", "toml", "json"):
+ pathname = os.path.join(d, basename + "." + ext)
+ if os.path.exists(pathname):
+ logf("%s", f'Found "{pathname}"')
+ break
+ else:
+ continue
+ break
+ else:
+ raise FileNotFoundError(basename + ".{json,toml,yaml} in " + f"{search}")
+
+ _, ext = pathname.rsplit(".", 1)
+
+ if ext == "json":
+ config = json.load(open(pathname, encoding="utf-8"))
+ elif ext == "toml":
+ import toml # pylint: disable=C0415
+
+ config = toml.load(pathname)
+ elif ext == "yaml":
+ import yaml # pylint: disable=C0415
+
+ config = yaml.safe_load(open(pathname, encoding="utf-8"))
+ else:
+ raise ValueError("Filename does not end with (.json|.toml|.yaml)")
+
+ config["config_pathname"] = os.path.realpath(pathname)
+ return config
+
+
+def setup_logging(args, config_base="logconf"):
+ # Create rundir and arrange for future commands to run in it.
+
+ # Change CWD to the rundir prior to parsing config
+ old = os.getcwd()
+ os.chdir(args.rundir)
+ try:
+ search = [old]
+ with importlib.resources.path("munet", config_base + ".yaml") as datapath:
+ search.append(str(datapath.parent))
+
+ def logf(msg, *p, **k):
+ if args.verbose:
+ print("PRELOG: " + msg % p, **k, file=sys.stderr)
+
+ config = get_config(args.log_config, config_base, search, logf=logf)
+ pathname = config["config_pathname"]
+ del config["config_pathname"]
+
+ if "info_console" in config["handlers"]:
+ # mutest case
+ if args.verbose > 1:
+ config["handlers"]["console"]["level"] = "DEBUG"
+ config["handlers"]["info_console"]["level"] = "DEBUG"
+ elif args.verbose:
+ config["handlers"]["console"]["level"] = "INFO"
+ config["handlers"]["info_console"]["level"] = "DEBUG"
+ elif args.verbose:
+ # munet case
+ config["handlers"]["console"]["level"] = "DEBUG"
+
+ # add the rundir path to the filenames
+ for v in config["handlers"].values():
+ filename = v.get("filename")
+ if not filename:
+ continue
+ v["filename"] = os.path.join(args.rundir, filename)
+
+ logging.config.dictConfig(dict(config))
+ logging.info("Loaded logging config %s", pathname)
+
+ return config
+ finally:
+ os.chdir(old)
+
+
+def append_hosts_files(unet, netname):
+ if not netname:
+ return
+
+ entries = []
+ for name in ("munet", *list(unet.hosts)):
+ if name == "munet":
+ node = unet.switches[netname]
+ ifname = None
+ else:
+ node = unet.hosts[name]
+ if not hasattr(node, "_intf_addrs"):
+ continue
+ ifname = node.get_ifname(netname)
+
+ for b in (False, True):
+ ifaddr = node.get_intf_addr(ifname, ipv6=b)
+ if ifaddr and hasattr(ifaddr, "ip"):
+ entries.append((name, ifaddr.ip))
+
+ for name in ("munet", *list(unet.hosts)):
+ node = unet if name == "munet" else unet.hosts[name]
+ if not hasattr(node, "rundir"):
+ continue
+ with open(os.path.join(node.rundir, "hosts.txt"), "a+", encoding="ascii") as hf:
+ hf.write("\n")
+ for e in entries:
+ hf.write(f"{e[1]}\t{e[0]}\n")
+
+
+def validate_config(config, logger, args):
+ if jsonschema is None:
+ logger.debug("No validation w/o jsonschema module")
+ return True
+
+ old = os.getcwd()
+ if args:
+ os.chdir(args.rundir)
+
+ try:
+ validator = jsonschema.validators.Draft202012Validator(get_schema())
+ validator.validate(instance=config)
+ logger.debug("Validated %s", config["config_pathname"])
+ return True
+ except FileNotFoundError as error:
+ logger.info("No schema found: %s", error)
+ return False
+ except ValidationError as error:
+ logger.info("Validation failed: %s", error)
+ return False
+ finally:
+ if args:
+ os.chdir(old)
+
+
+def load_kinds(args, search=None):
+ # Change CWD to the rundir prior to parsing config
+ cwd = os.getcwd()
+ if args:
+ os.chdir(args.rundir)
+
+ args_config = args.kinds_config if args else None
+ try:
+ if search is None:
+ search = [cwd]
+ with importlib.resources.path("munet", "kinds.yaml") as datapath:
+ search.insert(0, str(datapath.parent))
+
+ configs = []
+ if args_config:
+ configs.append(get_config(args_config, "kinds", search=[]))
+ else:
+ # prefer directories at the front of the list
+ for kdir in search:
+ try:
+ configs.append(get_config(basename="kinds", search=[kdir]))
+ except FileNotFoundError:
+ continue
+
+ kinds = {}
+ for config in configs:
+ # XXX need to fix the issue with `connections: ["net0"]` not validating
+ # if jsonschema is not None:
+ # validator = jsonschema.validators.Draft202012Validator(get_schema())
+ # validator.validate(instance=config)
+
+ kinds_list = config.get("kinds", [])
+ kinds_dict = list_to_dict_with_key(kinds_list, "name")
+ if kinds_dict:
+ logging.info("Loading kinds config from %s", config["config_pathname"])
+ if "kinds" in kinds:
+ kinds["kinds"].update(**kinds_dict)
+ else:
+ kinds["kinds"] = kinds_dict
+
+ cli_list = config.get("cli", {}).get("commands", [])
+ if cli_list:
+ logging.info("Loading cli comands from %s", config["config_pathname"])
+ if "cli" not in kinds:
+ kinds["cli"] = {}
+ if "commands" not in kinds["cli"]:
+ kinds["cli"]["commands"] = []
+ kinds["cli"]["commands"].extend(cli_list)
+
+ return kinds
+ except FileNotFoundError as error:
+ # if we have kinds in args but the file doesn't exist, raise the error
+ if args_config is not None:
+ raise error
+ return {}
+ finally:
+ if args:
+ os.chdir(cwd)
+
+
+async def async_build_topology(
+ config=None,
+ logger=None,
+ rundir=None,
+ args=None,
+ unshare_inline=False,
+ pytestconfig=None,
+ search_root=None,
+ top_level_pidns=True,
+):
+
+ if not rundir:
+ rundir = tempfile.mkdtemp(prefix="unet")
+ subprocess.run(f"mkdir -p {rundir} && chmod 755 {rundir}", check=True, shell=True)
+
+ isolated = not args.host if args else True
+ if not config:
+ config = get_config(basename="munet")
+
+ # create search directories from common root if given
+ cpath = Path(config["config_pathname"]).absolute()
+ project_root = args.project_root if args else None
+ if not search_root:
+ search_root = find_project_root(cpath, project_root)
+ if not search_root:
+ search = [cpath.parent]
+ else:
+ search_root = Path(search_root).absolute()
+ if search_root in cpath.parents:
+ search = list(cpath.parents)
+ if remcount := len(search_root.parents):
+ search = search[0:-remcount]
+
+ # load kinds along search path and merge into config
+ kinds = load_kinds(args, search=search)
+ config_kinds_dict = list_to_dict_with_key(config.get("kinds", []), "name")
+ config["kinds"] = {**kinds.get("kinds", {}), **config_kinds_dict}
+
+ # mere CLI command from kinds into config as well.
+ kinds_cli_list = kinds.get("cli", {}).get("commands", [])
+ config_cli_list = config.get("cli", {}).get("commands", [])
+ if config_cli_list:
+ if kinds_cli_list:
+ config_cli_list.extend(list(kinds_cli_list))
+ elif kinds_cli_list:
+ if "cli" not in config:
+ config["cli"] = {}
+ if "commands" not in config["cli"]:
+ config["cli"]["commands"] = []
+ config["cli"]["commands"].extend(list(kinds_cli_list))
+
+ unet = Munet(
+ rundir=rundir,
+ config=config,
+ pytestconfig=pytestconfig,
+ isolated=isolated,
+ pid=top_level_pidns,
+ unshare_inline=args.unshare_inline if args else unshare_inline,
+ logger=logger,
+ )
+
+ try:
+ await unet._async_build(logger) # pylint: disable=W0212
+ except Exception as error:
+ logging.critical("Failure building munet topology: %s", error, exc_info=True)
+ await unet.async_delete()
+ raise
+ except KeyboardInterrupt:
+ await unet.async_delete()
+ raise
+
+ topoconf = config.get("topology")
+ if not topoconf:
+ return unet
+
+ dns_network = topoconf.get("dns-network")
+ if dns_network:
+ append_hosts_files(unet, dns_network)
+
+ # Write our current config to the run directory
+ with open(f"{unet.rundir}/config.json", "w", encoding="utf-8") as f:
+ json.dump(unet.config, f, indent=2)
+
+ return unet
+
+
+def build_topology(config=None, logger=None, rundir=None, args=None, pytestconfig=None):
+ return asyncio.run(async_build_topology(config, logger, rundir, args, pytestconfig))
diff --git a/tests/topotests/munet/testing/__init__.py b/tests/topotests/munet/testing/__init__.py
new file mode 100644
index 0000000000..63cbfabda9
--- /dev/null
+++ b/tests/topotests/munet/testing/__init__.py
@@ -0,0 +1 @@
+"""Sub-package supporting munet use in pytest."""
diff --git a/tests/topotests/munet/testing/fixtures.py b/tests/topotests/munet/testing/fixtures.py
new file mode 100644
index 0000000000..25039df541
--- /dev/null
+++ b/tests/topotests/munet/testing/fixtures.py
@@ -0,0 +1,447 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# April 22 2022, Christian Hopps <chopps@gmail.com>
+#
+# Copyright (c) 2022, LabN Consulting, L.L.C
+#
+"""A module that implements pytest fixtures.
+
+To use in your project, in your conftest.py add:
+
+ from munet.testing.fixtures import *
+"""
+import contextlib
+import logging
+import os
+
+from pathlib import Path
+from typing import Union
+
+import pytest
+import pytest_asyncio
+
+from ..base import BaseMunet
+from ..base import Bridge
+from ..base import get_event_loop
+from ..cleanup import cleanup_current
+from ..cleanup import cleanup_previous
+from ..native import L3NodeMixin
+from ..parser import async_build_topology
+from ..parser import get_config
+from .util import async_pause_test
+from .util import pause_test
+
+
+@contextlib.asynccontextmanager
+async def achdir(ndir: Union[str, Path], desc=""):
+ odir = os.getcwd()
+ os.chdir(ndir)
+ if desc:
+ logging.debug("%s: chdir from %s to %s", desc, odir, ndir)
+ try:
+ yield
+ finally:
+ if desc:
+ logging.debug("%s: chdir back from %s to %s", desc, ndir, odir)
+ os.chdir(odir)
+
+
+@contextlib.contextmanager
+def chdir(ndir: Union[str, Path], desc=""):
+ odir = os.getcwd()
+ os.chdir(ndir)
+ if desc:
+ logging.debug("%s: chdir from %s to %s", desc, odir, ndir)
+ try:
+ yield
+ finally:
+ if desc:
+ logging.debug("%s: chdir back from %s to %s", desc, ndir, odir)
+ os.chdir(odir)
+
+
+def get_test_logdir(nodeid=None, module=False):
+ """Get log directory relative pathname."""
+ xdist_worker = os.getenv("PYTEST_XDIST_WORKER", "")
+ mode = os.getenv("PYTEST_XDIST_MODE", "no")
+
+ # nodeid: all_protocol_startup/test_all_protocol_startup.py::test_router_running
+ # may be missing "::testname" if module is True
+ if not nodeid:
+ nodeid = os.environ["PYTEST_CURRENT_TEST"].split(" ")[0]
+
+ cur_test = nodeid.replace("[", "_").replace("]", "_")
+ if module:
+ idx = cur_test.rfind("::")
+ path = cur_test if idx == -1 else cur_test[:idx]
+ testname = ""
+ else:
+ path, testname = cur_test.split("::")
+ testname = testname.replace("/", ".")
+ path = path[:-3].replace("/", ".")
+
+ # We use different logdir paths based on how xdist is running.
+ if mode == "each":
+ if module:
+ return os.path.join(path, "worker-logs", xdist_worker)
+ return os.path.join(path, testname, xdist_worker)
+ assert mode in ("no", "load", "loadfile", "loadscope"), f"Unknown dist mode {mode}"
+ return path if module else os.path.join(path, testname)
+
+
+def _push_log_handler(desc, logpath):
+ logpath = os.path.abspath(logpath)
+ logging.debug("conftest: adding %s logging at %s", desc, logpath)
+ root_logger = logging.getLogger()
+ handler = logging.FileHandler(logpath, mode="w")
+ fmt = logging.Formatter("%(asctime)s %(levelname)5s: %(message)s")
+ handler.setFormatter(fmt)
+ root_logger.addHandler(handler)
+ return handler
+
+
+def _pop_log_handler(handler):
+ root_logger = logging.getLogger()
+ logging.debug("conftest: removing logging handler %s", handler)
+ root_logger.removeHandler(handler)
+
+
+@contextlib.contextmanager
+def log_handler(desc, logpath):
+ handler = _push_log_handler(desc, logpath)
+ try:
+ yield
+ finally:
+ _pop_log_handler(handler)
+
+
+# =================
+# Sessions Fixtures
+# =================
+
+
+@pytest.fixture(autouse=True, scope="session")
+def session_autouse():
+ if "PYTEST_TOPOTEST_WORKER" not in os.environ:
+ is_worker = False
+ elif not os.environ["PYTEST_TOPOTEST_WORKER"]:
+ is_worker = False
+ else:
+ is_worker = True
+
+ if not is_worker:
+ # This is unfriendly to multi-instance
+ cleanup_previous()
+
+ # We never pop as we want to keep logging
+ _push_log_handler("session", "/tmp/unet-test/pytest-session.log")
+
+ yield
+
+ if not is_worker:
+ cleanup_current()
+
+
+# ===============
+# Module Fixtures
+# ===============
+
+
+@pytest.fixture(autouse=True, scope="module")
+def module_autouse(request):
+ logpath = get_test_logdir(request.node.name, True)
+ logpath = os.path.join("/tmp/unet-test", logpath, "pytest-exec.log")
+ with log_handler("module", logpath):
+ sdir = os.path.dirname(os.path.realpath(request.fspath))
+ with chdir(sdir, "module autouse fixture"):
+ yield
+
+ if BaseMunet.g_unet:
+ raise Exception("Base Munet was not cleaned up/deleted")
+
+
+@pytest.fixture(scope="module")
+def event_loop():
+ """Create an instance of the default event loop for the session."""
+ loop = get_event_loop()
+ try:
+ logging.info("event_loop_fixture: yielding with new event loop watcher")
+ yield loop
+ finally:
+ loop.close()
+
+
+@pytest.fixture(scope="module")
+def rundir_module():
+ d = os.path.join("/tmp/unet-test", get_test_logdir(module=True))
+ logging.debug("conftest: test module rundir %s", d)
+ return d
+
+
+async def _unet_impl(
+ _rundir, _pytestconfig, unshare=None, top_level_pidns=None, param=None
+):
+ try:
+ # Default is not to unshare inline if not specified otherwise
+ unshare_default = False
+ pidns_default = True
+ if isinstance(param, (tuple, list)):
+ pidns_default = bool(param[2]) if len(param) > 2 else True
+ unshare_default = bool(param[1]) if len(param) > 1 else False
+ param = str(param[0])
+ elif isinstance(param, bool):
+ unshare_default = param
+ param = None
+ if unshare is None:
+ unshare = unshare_default
+ if top_level_pidns is None:
+ top_level_pidns = pidns_default
+
+ logging.info("unet fixture: basename=%s unshare_inline=%s", param, unshare)
+ _unet = await async_build_topology(
+ config=get_config(basename=param) if param else None,
+ rundir=_rundir,
+ unshare_inline=unshare,
+ top_level_pidns=top_level_pidns,
+ pytestconfig=_pytestconfig,
+ )
+ except Exception as error:
+ logging.debug(
+ "unet fixture: unet build failed: %s\nparam: %s",
+ error,
+ param,
+ exc_info=True,
+ )
+ pytest.skip(
+ f"unet fixture: unet build failed: {error}", allow_module_level=True
+ )
+ raise
+
+ try:
+ tasks = await _unet.run()
+ except Exception as error:
+ logging.debug("unet fixture: unet run failed: %s", error, exc_info=True)
+ await _unet.async_delete()
+ pytest.skip(f"unet fixture: unet run failed: {error}", allow_module_level=True)
+ raise
+
+ logging.debug("unet fixture: containers running")
+
+ # Pytest is supposed to always return even if exceptions
+ try:
+ yield _unet
+ except Exception as error:
+ logging.error("unet fixture: yield unet unexpected exception: %s", error)
+
+ logging.debug("unet fixture: module done, deleting unet")
+ await _unet.async_delete()
+
+ # No one ever awaits these so cancel them
+ logging.debug("unet fixture: cleanup")
+ for task in tasks:
+ task.cancel()
+
+ # Reset the class variables so auto number is predictable
+ logging.debug("unet fixture: resetting ords to 1")
+ L3NodeMixin.next_ord = 1
+ Bridge.next_ord = 1
+
+
+@pytest.fixture(scope="module")
+async def unet(request, rundir_module, pytestconfig): # pylint: disable=W0621
+ """A unet creating fixutre.
+
+ The request param is either the basename of the config file or a tuple of the form:
+ (basename, unshare, top_level_pidns), with the second and third elements boolean and
+ optional, defaulting to False, True.
+ """
+ param = request.param if hasattr(request, "param") else None
+ sdir = os.path.dirname(os.path.realpath(request.fspath))
+ async with achdir(sdir, "unet fixture"):
+ async for x in _unet_impl(rundir_module, pytestconfig, param=param):
+ yield x
+
+
+@pytest.fixture(scope="module")
+async def unet_share(request, rundir_module, pytestconfig): # pylint: disable=W0621
+ """A unet creating fixutre.
+
+ This share variant keeps munet from unsharing the process to a new namespace so that
+ root level commands and actions are execute on the host, normally they are executed
+ in the munet namespace which allowing things like scapy inline in tests to work.
+
+ The request param is either the basename of the config file or a tuple of the form:
+ (basename, top_level_pidns), the second value is a boolean.
+ """
+ param = request.param if hasattr(request, "param") else None
+ if isinstance(param, (tuple, list)):
+ param = (param[0], False, param[1])
+ sdir = os.path.dirname(os.path.realpath(request.fspath))
+ async with achdir(sdir, "unet_share fixture"):
+ async for x in _unet_impl(
+ rundir_module, pytestconfig, unshare=False, param=param
+ ):
+ yield x
+
+
+@pytest.fixture(scope="module")
+async def unet_unshare(request, rundir_module, pytestconfig): # pylint: disable=W0621
+ """A unet creating fixutre.
+
+ This unshare variant has the top level munet unshare the process inline so that
+ root level commands and actions are execute in a new namespace. This allows things
+ like scapy inline in tests to work.
+
+ The request param is either the basename of the config file or a tuple of the form:
+ (basename, top_level_pidns), the second value is a boolean.
+ """
+ param = request.param if hasattr(request, "param") else None
+ if isinstance(param, (tuple, list)):
+ param = (param[0], True, param[1])
+ sdir = os.path.dirname(os.path.realpath(request.fspath))
+ async with achdir(sdir, "unet_unshare fixture"):
+ async for x in _unet_impl(
+ rundir_module, pytestconfig, unshare=True, param=param
+ ):
+ yield x
+
+
+# =================
+# Function Fixtures
+# =================
+
+
+@pytest.fixture(autouse=True, scope="function")
+async def function_autouse(request):
+ async with achdir(
+ os.path.dirname(os.path.realpath(request.fspath)), "func.fixture"
+ ):
+ yield
+
+
+@pytest.fixture(autouse=True)
+async def check_for_pause(request, pytestconfig):
+ # When we unshare inline we can't pause in the pytest_runtest_makereport hook
+ # so do it here.
+ if BaseMunet.g_unet and BaseMunet.g_unet.unshare_inline:
+ pause = bool(pytestconfig.getoption("--pause"))
+ if pause:
+ await async_pause_test(f"XXX before test '{request.node.name}'")
+ yield
+
+
+@pytest.fixture(scope="function")
+def stepf(pytestconfig):
+ class Stepnum:
+ """Track the stepnum in closure."""
+
+ num = 0
+
+ def inc(self):
+ self.num += 1
+
+ pause = pytestconfig.getoption("pause")
+ stepnum = Stepnum()
+
+ def stepfunction(desc=""):
+ desc = f": {desc}" if desc else ""
+ if pause:
+ pause_test(f"before step {stepnum.num}{desc}")
+ logging.info("STEP %s%s", stepnum.num, desc)
+ stepnum.inc()
+
+ return stepfunction
+
+
+@pytest_asyncio.fixture(scope="function")
+async def astepf(pytestconfig):
+ class Stepnum:
+ """Track the stepnum in closure."""
+
+ num = 0
+
+ def inc(self):
+ self.num += 1
+
+ pause = pytestconfig.getoption("pause")
+ stepnum = Stepnum()
+
+ async def stepfunction(desc=""):
+ desc = f": {desc}" if desc else ""
+ if pause:
+ await async_pause_test(f"before step {stepnum.num}{desc}")
+ logging.info("STEP %s%s", stepnum.num, desc)
+ stepnum.inc()
+
+ return stepfunction
+
+
+@pytest.fixture(scope="function")
+def rundir():
+ d = os.path.join("/tmp/unet-test", get_test_logdir(module=False))
+ logging.debug("conftest: test function rundir %s", d)
+ return d
+
+
+# Configure logging
+@pytest.hookimpl(hookwrapper=True, tryfirst=True)
+def pytest_runtest_setup(item):
+ d = os.path.join(
+ "/tmp/unet-test", get_test_logdir(nodeid=item.nodeid, module=False)
+ )
+ config = item.config
+ logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
+ filename = Path(d, "pytest-exec.log")
+ logging_plugin.set_log_path(str(filename))
+ logging.debug("conftest: test function setup: rundir %s", d)
+ yield
+
+
+@pytest.fixture
+async def unet_perfunc(request, rundir, pytestconfig): # pylint: disable=W0621
+ param = request.param if hasattr(request, "param") else None
+ async for x in _unet_impl(rundir, pytestconfig, param=param):
+ yield x
+
+
+@pytest.fixture
+async def unet_perfunc_unshare(request, rundir, pytestconfig): # pylint: disable=W0621
+ """Build unet per test function with an optional topology basename parameter.
+
+ The fixture can be parameterized to choose different config files.
+ For example, use as follows to run the test with unet_perfunc configured
+ first with a config file named `cfg1.yaml` then with config file `cfg2.yaml`
+ (where the actual files could end with `json` or `toml` rather than `yaml`).
+
+ @pytest.mark.parametrize(
+ "unet_perfunc", ["cfg1", "cfg2]", indirect=["unet_perfunc"]
+ )
+ def test_example(unet_perfunc)
+ """
+ param = request.param if hasattr(request, "param") else None
+ async for x in _unet_impl(rundir, pytestconfig, unshare=True, param=param):
+ yield x
+
+
+@pytest.fixture
+async def unet_perfunc_share(request, rundir, pytestconfig): # pylint: disable=W0621
+ """Build unet per test function with an optional topology basename parameter.
+
+ This share variant keeps munet from unsharing the process to a new namespace so that
+ root level commands and actions are execute on the host, normally they are executed
+ in the munet namespace which allowing things like scapy inline in tests to work.
+
+ The fixture can be parameterized to choose different config files. For example, use
+ as follows to run the test with unet_perfunc configured first with a config file
+ named `cfg1.yaml` then with config file `cfg2.yaml` (where the actual files could
+ end with `json` or `toml` rather than `yaml`).
+
+ @pytest.mark.parametrize(
+ "unet_perfunc", ["cfg1", "cfg2]", indirect=["unet_perfunc"]
+ )
+ def test_example(unet_perfunc)
+ """
+ param = request.param if hasattr(request, "param") else None
+ async for x in _unet_impl(rundir, pytestconfig, unshare=False, param=param):
+ yield x
diff --git a/tests/topotests/munet/testing/hooks.py b/tests/topotests/munet/testing/hooks.py
new file mode 100644
index 0000000000..9b6a49a18c
--- /dev/null
+++ b/tests/topotests/munet/testing/hooks.py
@@ -0,0 +1,225 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# April 22 2022, Christian Hopps <chopps@gmail.com>
+#
+# Copyright (c) 2022, LabN Consulting, L.L.C
+#
+"""A module that implements pytest hooks.
+
+To use in your project, in your conftest.py add:
+
+ from munet.testing.hooks import *
+"""
+import logging
+import os
+import sys
+import traceback
+
+import pytest
+
+from ..base import BaseMunet # pylint: disable=import-error
+from ..cli import cli # pylint: disable=import-error
+from .util import pause_test
+
+
+# ===================
+# Hooks (non-fixture)
+# ===================
+
+
+def pytest_addoption(parser):
+ parser.addoption(
+ "--cli-on-error",
+ action="store_true",
+ help="CLI on test failure",
+ )
+
+ parser.addoption(
+ "--coverage",
+ action="store_true",
+ help="Enable coverage gathering if supported",
+ )
+
+ parser.addoption(
+ "--gdb",
+ default="",
+ metavar="HOST[,HOST...]",
+ help="Comma-separated list of nodes to launch gdb on, or 'all'",
+ )
+ parser.addoption(
+ "--gdb-breakpoints",
+ default="",
+ metavar="BREAKPOINT[,BREAKPOINT...]",
+ help="Comma-separated list of breakpoints",
+ )
+ parser.addoption(
+ "--gdb-use-emacs",
+ action="store_true",
+ help="Use emacsclient to run gdb instead of a shell",
+ )
+
+ parser.addoption(
+ "--pcap",
+ default="",
+ metavar="NET[,NET...]",
+ help="Comma-separated list of networks to capture packets on, or 'all'",
+ )
+
+ parser.addoption(
+ "--pause",
+ action="store_true",
+ help="Pause after each test",
+ )
+ parser.addoption(
+ "--pause-at-end",
+ action="store_true",
+ help="Pause before taking munet down",
+ )
+ parser.addoption(
+ "--pause-on-error",
+ action="store_true",
+ help="Pause after (disables default when --shell or -vtysh given)",
+ )
+ parser.addoption(
+ "--no-pause-on-error",
+ dest="pause_on_error",
+ action="store_false",
+ help="Do not pause after (disables default when --shell or -vtysh given)",
+ )
+
+ parser.addoption(
+ "--shell",
+ default="",
+ metavar="NODE[,NODE...]",
+ help="Comma-separated list of nodes to spawn shell on, or 'all'",
+ )
+
+ parser.addoption(
+ "--stdout",
+ default="",
+ metavar="NODE[,NODE...]",
+ help="Comma-separated list of nodes to open tail-f stdout window on, or 'all'",
+ )
+
+ parser.addoption(
+ "--stderr",
+ default="",
+ metavar="NODE[,NODE...]",
+ help="Comma-separated list of nodes to open tail-f stderr window on, or 'all'",
+ )
+
+
+def pytest_configure(config):
+ if "PYTEST_XDIST_WORKER" not in os.environ:
+ os.environ["PYTEST_XDIST_MODE"] = config.getoption("dist", "no")
+ os.environ["PYTEST_IS_WORKER"] = ""
+ is_xdist = os.environ["PYTEST_XDIST_MODE"] != "no"
+ is_worker = False
+ else:
+ os.environ["PYTEST_IS_WORKER"] = os.environ["PYTEST_XDIST_WORKER"]
+ is_xdist = True
+ is_worker = True
+
+ # Turn on live logging if user specified verbose and the config has a CLI level set
+ if config.getoption("--verbose") and not is_xdist and not config.getini("log_cli"):
+ if config.getoption("--log-cli-level", None) is None:
+ # By setting the CLI option to the ini value it enables log_cli=1
+ cli_level = config.getini("log_cli_level")
+ if cli_level is not None:
+ config.option.log_cli_level = cli_level
+
+ have_tmux = bool(os.getenv("TMUX", ""))
+ have_screen = not have_tmux and bool(os.getenv("STY", ""))
+ have_xterm = not have_tmux and not have_screen and bool(os.getenv("DISPLAY", ""))
+ have_windows = have_tmux or have_screen or have_xterm
+ have_windows_pause = have_tmux or have_xterm
+ xdist_no_windows = is_xdist and not is_worker and not have_windows_pause
+
+ for winopt in ["--shell", "--stdout", "--stderr"]:
+ b = config.getoption(winopt)
+ if b and xdist_no_windows:
+ pytest.exit(
+ f"{winopt} use requires byobu/TMUX/XTerm "
+ f"under dist {os.environ['PYTEST_XDIST_MODE']}"
+ )
+ elif b and not is_xdist and not have_windows:
+ pytest.exit(f"{winopt} use requires byobu/TMUX/SCREEN/XTerm")
+
+
+def pytest_runtest_makereport(item, call):
+ """Pause or invoke CLI as directed by config."""
+ isatty = sys.stdout.isatty()
+
+ pause = bool(item.config.getoption("--pause"))
+ skipped = False
+
+ if call.excinfo is None:
+ error = False
+ elif call.excinfo.typename == "Skipped":
+ skipped = True
+ error = False
+ pause = False
+ else:
+ error = True
+ modname = item.parent.module.__name__
+ exval = call.excinfo.value
+ logging.error(
+ "test %s/%s failed: %s: stdout: '%s' stderr: '%s'",
+ modname,
+ item.name,
+ exval,
+ exval.stdout if hasattr(exval, "stdout") else "NA",
+ exval.stderr if hasattr(exval, "stderr") else "NA",
+ )
+ if not pause:
+ pause = item.config.getoption("--pause-on-error")
+
+ if error and isatty and item.config.getoption("--cli-on-error"):
+ if not BaseMunet.g_unet:
+ logging.error("Could not launch CLI b/c no munet exists yet")
+ else:
+ print(f"\nCLI-ON-ERROR: {call.excinfo.typename}")
+ print(f"CLI-ON-ERROR:\ntest {modname}/{item.name} failed: {exval}")
+ if hasattr(exval, "stdout") and exval.stdout:
+ print("stdout: " + exval.stdout.replace("\n", "\nstdout: "))
+ if hasattr(exval, "stderr") and exval.stderr:
+ print("stderr: " + exval.stderr.replace("\n", "\nstderr: "))
+ cli(BaseMunet.g_unet)
+
+ if pause:
+ if skipped:
+ item.skip_more_pause = True
+ elif hasattr(item, "skip_more_pause"):
+ pass
+ elif call.when == "setup":
+ if error:
+ item.skip_more_pause = True
+
+ # we can't asyncio.run() (which pause does) if we are unhsare_inline
+ # at this point, count on an autouse fixture to pause instead in this
+ # case
+ if not BaseMunet.g_unet or not BaseMunet.g_unet.unshare_inline:
+ pause_test(f"before test '{item.nodeid}'")
+
+ # check for a result to try and catch setup (or module setup) failure
+ # e.g., after a module level fixture fails, we do not want to pause on every
+ # skipped test.
+ elif call.when == "teardown" and call.excinfo:
+ logging.warning(
+ "Caught exception during teardown: %s\n:Traceback:\n%s",
+ call.excinfo,
+ "".join(traceback.format_tb(call.excinfo.tb)),
+ )
+ pause_test(f"after teardown after test '{item.nodeid}'")
+ elif call.when == "teardown" and call.result:
+ pause_test(f"after test '{item.nodeid}'")
+ elif error:
+ item.skip_more_pause = True
+ print(f"\nPAUSE-ON-ERROR: {call.excinfo.typename}")
+ print(f"PAUSE-ON-ERROR:\ntest {modname}/{item.name} failed: {exval}")
+ if hasattr(exval, "stdout") and exval.stdout:
+ print("stdout: " + exval.stdout.replace("\n", "\nstdout: "))
+ if hasattr(exval, "stderr") and exval.stderr:
+ print("stderr: " + exval.stderr.replace("\n", "\nstderr: "))
+ pause_test(f"PAUSE-ON-ERROR: '{item.nodeid}'")
diff --git a/tests/topotests/munet/testing/util.py b/tests/topotests/munet/testing/util.py
new file mode 100644
index 0000000000..a1a94bcd1b
--- /dev/null
+++ b/tests/topotests/munet/testing/util.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# April 22 2022, Christian Hopps <chopps@gmail.com>
+#
+# Copyright (c) 2022, LabN Consulting, L.L.C
+#
+"""Utility functions useful when using munet testing functionailty in pytest."""
+import asyncio
+import datetime
+import functools
+import logging
+import sys
+import time
+
+from ..base import BaseMunet
+from ..cli import async_cli
+
+
+# =================
+# Utility Functions
+# =================
+
+
+async def async_pause_test(desc=""):
+ isatty = sys.stdout.isatty()
+ if not isatty:
+ desc = f" for {desc}" if desc else ""
+ logging.info("NO PAUSE on non-tty terminal%s", desc)
+ return
+
+ while True:
+ if desc:
+ print(f"\n== PAUSING: {desc} ==")
+ try:
+ user = input('PAUSED, "cli" for CLI, "pdb" to debug, "Enter" to continue: ')
+ except EOFError:
+ print("^D...continuing")
+ break
+ user = user.strip()
+ if user == "cli":
+ await async_cli(BaseMunet.g_unet)
+ elif user == "pdb":
+ breakpoint() # pylint: disable=W1515
+ elif user:
+ print(f'Unrecognized input: "{user}"')
+ else:
+ break
+
+
+def pause_test(desc=""):
+ asyncio.run(async_pause_test(desc))
+
+
+def retry(retry_timeout, initial_wait=0, expected=True):
+ """decorator: retry while functions return is not None or raises an exception.
+
+ * `retry_timeout`: Retry for at least this many seconds; after waiting
+ initial_wait seconds
+ * `initial_wait`: Sleeps for this many seconds before first executing function
+ * `expected`: if False then the return logic is inverted, except for exceptions,
+ (i.e., a non None ends the retry loop, and returns that value)
+ """
+
+ def _retry(func):
+ @functools.wraps(func)
+ def func_retry(*args, **kwargs):
+ retry_sleep = 2
+
+ # Allow the wrapped function's args to override the fixtures
+ _retry_timeout = kwargs.pop("retry_timeout", retry_timeout)
+ _expected = kwargs.pop("expected", expected)
+ _initial_wait = kwargs.pop("initial_wait", initial_wait)
+ retry_until = datetime.datetime.now() + datetime.timedelta(
+ seconds=_retry_timeout + _initial_wait
+ )
+
+ if initial_wait > 0:
+ logging.info("Waiting for [%s]s as initial delay", initial_wait)
+ time.sleep(initial_wait)
+
+ while True:
+ seconds_left = (retry_until - datetime.datetime.now()).total_seconds()
+ try:
+ ret = func(*args, **kwargs)
+ if _expected and ret is None:
+ logging.debug("Function succeeds")
+ return ret
+ logging.debug("Function returned %s", ret)
+ except Exception as error:
+ logging.info("Function raised exception: %s", str(error))
+ ret = error
+
+ if seconds_left < 0:
+ logging.info("Retry timeout of %ds reached", _retry_timeout)
+ if isinstance(ret, Exception):
+ raise ret
+ return ret
+
+ logging.info(
+ "Sleeping %ds until next retry with %.1f retry time left",
+ retry_sleep,
+ seconds_left,
+ )
+ time.sleep(retry_sleep)
+
+ func_retry._original = func # pylint: disable=W0212
+ return func_retry
+
+ return _retry
diff --git a/tests/topotests/ospf6_ecmp_inter_area/r1/ospf6d.conf b/tests/topotests/ospf6_ecmp_inter_area/r1/ospf6d.conf
index 6c7cb96240..e6e5733010 100644
--- a/tests/topotests/ospf6_ecmp_inter_area/r1/ospf6d.conf
+++ b/tests/topotests/ospf6_ecmp_inter_area/r1/ospf6d.conf
@@ -1,35 +1,35 @@
-debug ospf6 lsa all
-debug ospf6 message all
-debug ospf6 route all
-debug ospf6 spf time
-debug ospf6 spf database
-debug ospf6 zebra send
-debug ospf6 zebra recv
-
-debug ospf6 lsa router
-debug ospf6 lsa router originate
-debug ospf6 lsa router examine
-debug ospf6 lsa router flooding
-debug ospf6 lsa as-external
-debug ospf6 lsa as-external originate
-debug ospf6 lsa as-external examine
-debug ospf6 lsa as-external flooding
-debug ospf6 lsa intra-prefix
-debug ospf6 lsa intra-prefix originate
-debug ospf6 lsa intra-prefix examine
-debug ospf6 lsa intra-prefix flooding
-debug ospf6 border-routers
-debug ospf6 zebra
-debug ospf6 interface
-debug ospf6 neighbor
-debug ospf6 flooding
-debug ospf6 gr helper
-debug ospf6 spf process
-debug ospf6 route intra-area
-debug ospf6 route inter-area
-debug ospf6 abr
-debug ospf6 asbr
-debug ospf6 nssa
+!debug ospf6 lsa all
+!debug ospf6 message all
+!debug ospf6 route all
+!debug ospf6 spf time
+!debug ospf6 spf database
+!debug ospf6 zebra send
+!debug ospf6 zebra recv
+!
+!debug ospf6 lsa router
+!debug ospf6 lsa router originate
+!debug ospf6 lsa router examine
+!debug ospf6 lsa router flooding
+!debug ospf6 lsa as-external
+!debug ospf6 lsa as-external originate
+!debug ospf6 lsa as-external examine
+!debug ospf6 lsa as-external flooding
+!debug ospf6 lsa intra-prefix
+!debug ospf6 lsa intra-prefix originate
+!debug ospf6 lsa intra-prefix examine
+!debug ospf6 lsa intra-prefix flooding
+!debug ospf6 border-routers
+!debug ospf6 zebra
+!debug ospf6 interface
+!debug ospf6 neighbor
+!debug ospf6 flooding
+!debug ospf6 gr helper
+!debug ospf6 spf process
+!debug ospf6 route intra-area
+!debug ospf6 route inter-area
+!debug ospf6 abr
+!debug ospf6 asbr
+!debug ospf6 nssa
!
interface r1-eth0
ipv6 ospf6 area 0
diff --git a/tests/topotests/ospf_basic_functionality/ospf_lan.json b/tests/topotests/ospf_basic_functionality/ospf_lan.json
index 126934c344..54863382b2 100644
--- a/tests/topotests/ospf_basic_functionality/ospf_lan.json
+++ b/tests/topotests/ospf_basic_functionality/ospf_lan.json
@@ -18,7 +18,7 @@
"ospf": {
"area": "0.0.0.3",
"hello_interval": 1,
- "dead_interval": 4,
+ "dead_interval": 10,
"priority": 98
}
},
@@ -27,7 +27,7 @@
"ospf": {
"area": "0.0.0.3",
"hello_interval": 1,
- "dead_interval": 4,
+ "dead_interval": 10,
"priority": 99
}
},
@@ -36,7 +36,7 @@
"ospf": {
"area": "0.0.0.3",
"hello_interval": 1,
- "dead_interval": 4,
+ "dead_interval": 10,
"priority": 0
}
},
@@ -45,7 +45,7 @@
"ospf": {
"area": "0.0.0.3",
"hello_interval": 1,
- "dead_interval": 4,
+ "dead_interval": 10,
"priority": 0
}
}
@@ -135,4 +135,4 @@
}
}
}
-} \ No newline at end of file
+}
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py b/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py
index 3d15e94a51..9d7a15833c 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py
@@ -140,7 +140,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -260,11 +260,9 @@ def test_ospf_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
"ospf": {
@@ -290,7 +288,7 @@ def test_ospf_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -306,7 +304,7 @@ def test_ospf_type5_summary_tc43_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Change the summary address mask to lower match (ex - 16 to 8)")
ospf_summ_r1 = {
@@ -334,7 +332,7 @@ def test_ospf_type5_summary_tc43_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step(
"Verify that external routes(static / connected) are summarised"
@@ -350,7 +348,7 @@ def test_ospf_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Change the summary address mask to higher match (ex - 8 to 24)")
ospf_summ_r1 = {
@@ -378,7 +376,7 @@ def test_ospf_type5_summary_tc43_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step(
"Verify that external routes(static / connected) are summarised"
@@ -399,7 +397,7 @@ def test_ospf_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step(" Un configure one of the summary address.")
ospf_summ_r1 = {
@@ -432,7 +430,7 @@ def test_ospf_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
ospf_summ_r1 = {
"r0": {
@@ -459,7 +457,7 @@ def test_ospf_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
write_test_footer(tc_name)
@@ -506,11 +504,9 @@ def test_ospf_type5_summary_tc48_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
@@ -538,7 +534,7 @@ def test_ospf_type5_summary_tc48_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -554,26 +550,26 @@ def test_ospf_type5_summary_tc48_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv4"], "next_hop": "blackhole"}]}
}
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step(
"Configure route map and & rule to permit configured summary address,"
@@ -635,7 +631,7 @@ def test_ospf_type5_summary_tc48_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
input_dict = {
SUMMARY["ipv4"][0]: {
@@ -650,7 +646,7 @@ def test_ospf_type5_summary_tc48_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
write_test_footer(tc_name)
@@ -697,7 +693,7 @@ def test_ospf_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step(
"Configure External Route summary in R0 to summarise 5"
@@ -731,7 +727,7 @@ def test_ospf_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -747,26 +743,26 @@ def test_ospf_type5_summary_tc42_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv4"], "next_hop": "blackhole"}]}
}
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step("Delete the configured summary")
ospf_summ_r1 = {
@@ -789,19 +785,17 @@ def test_ospf_type5_summary_tc42_p0(request):
step("Verify that summary lsa is withdrawn from R1 and deleted from R0.")
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Summary Route still present in RIB".format(
- tc_name
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Summary Route should not present in RIB"
+ "Error: Summary Route still present in RIB".format(tc_name)
)
step("show ip ospf summary should not have any summary address.")
@@ -816,9 +810,10 @@ def test_ospf_type5_summary_tc42_p0(request):
}
dut = "r0"
result = verify_ospf_summary(tgen, topo, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Summary still present in DB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Summary Route should not present in OSPF DB"
+ "Error: Summary still present in DB".format(tc_name)
+ )
dut = "r1"
step("All 5 routes are advertised after deletion of configured summary.")
@@ -829,7 +824,7 @@ def test_ospf_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("configure the summary again and delete static routes .")
ospf_summ_r1 = {
@@ -857,7 +852,7 @@ def test_ospf_type5_summary_tc42_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
input_dict = {
"r0": {
@@ -874,18 +869,18 @@ def test_ospf_type5_summary_tc42_p0(request):
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step("Add back static routes.")
input_dict_static_rtes = {
@@ -901,18 +896,18 @@ def test_ospf_type5_summary_tc42_p0(request):
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_static_rtes, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
input_dict_summary = {"r0": {"static_routes": [{"network": SUMMARY["ipv4"][0]}]}}
dut = "r1"
@@ -923,7 +918,7 @@ def test_ospf_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show configure summaries.")
@@ -940,7 +935,7 @@ def test_ospf_type5_summary_tc42_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Configure new static route which is matching configured summary.")
input_dict_static_rtes = {
@@ -1004,7 +999,7 @@ def test_ospf_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Shut one of the interface")
intf = topo["routers"]["r0"]["links"]["r3-link0"]["interface"]
@@ -1076,7 +1071,7 @@ def test_ospf_type5_summary_tc42_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
input_dict_summary = {"r0": {"static_routes": [{"network": SUMMARY["ipv4"][0]}]}}
@@ -1087,7 +1082,7 @@ def test_ospf_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
ospf_summ_r1 = {
"r0": {
@@ -1113,18 +1108,18 @@ def test_ospf_type5_summary_tc42_p0(request):
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB. \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
ospf_summ_r1 = {
"r0": {
@@ -1188,11 +1183,9 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
"ospf": {
@@ -1224,7 +1217,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries with tag.")
input_dict = {
@@ -1240,7 +1233,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Delete the configured summary")
ospf_summ_r1 = {
@@ -1263,19 +1256,17 @@ def test_ospf_type5_summary_tc45_p0(request):
step("Verify that summary lsa is withdrawn from R1 and deleted from R0.")
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Summary Route still present in RIB".format(
- tc_name
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Summary Routes should not be present in RIB. \n"
+ "Error: Summary Route still present in RIB".format(tc_name)
)
step("show ip ospf summary should not have any summary address.")
@@ -1292,7 +1283,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary still present in DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary still present in DB".format(tc_name)
step("Configure Min tag value")
ospf_summ_r1 = {
@@ -1317,7 +1308,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries with tag.")
input_dict = {
@@ -1333,7 +1324,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Configure Max Tag Value")
ospf_summ_r1 = {
@@ -1363,7 +1354,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step(
"Verify that boundary values tags are used for summary route"
@@ -1382,7 +1373,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("configure new static route with different tag.")
input_dict_static_rtes_11 = {
@@ -1403,10 +1394,9 @@ def test_ospf_type5_summary_tc45_p0(request):
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, tag="88888", expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
@@ -1418,9 +1408,10 @@ def test_ospf_type5_summary_tc45_p0(request):
tag="88888",
expected=False,
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step(
"Verify that boundary values tags are used for summary route"
@@ -1439,7 +1430,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Delete the configured summary address")
ospf_summ_r1 = {
@@ -1470,24 +1461,24 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that summary address is flushed from neighbor.")
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step("Configure summary first & then configure matching static route.")
@@ -1589,7 +1580,7 @@ def test_ospf_type5_summary_tc45_p0(request):
assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result)
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -1619,11 +1610,9 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
"ospf": {
@@ -1655,7 +1644,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries with tag.")
input_dict = {
@@ -1691,19 +1680,17 @@ def test_ospf_type5_summary_tc45_p0(request):
step("Verify that summary lsa is withdrawn from R1 and deleted from R0.")
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Summary Route still present in RIB".format(
- tc_name
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB "
+ "Error: Summary Route still present in RIB".format(tc_name)
)
step("show ip ospf summary should not have any summary address.")
@@ -1720,7 +1707,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary still present in DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary still present in DB".format(tc_name)
step("Configure Min tag value")
ospf_summ_r1 = {
@@ -1745,7 +1732,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries with tag.")
input_dict = {
@@ -1761,7 +1748,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Configure Max Tag Value")
ospf_summ_r1 = {
@@ -1791,7 +1778,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step(
"Verify that boundary values tags are used for summary route"
@@ -1810,7 +1797,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("configure new static route with different tag.")
input_dict_static_rtes_11 = {
@@ -1831,10 +1818,9 @@ def test_ospf_type5_summary_tc45_p0(request):
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, tag="88888", expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
@@ -1846,9 +1832,10 @@ def test_ospf_type5_summary_tc45_p0(request):
tag="88888",
expected=False,
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in RIB.\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step(
"Verify that boundary values tags are used for summary route"
@@ -1867,7 +1854,7 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Delete the configured summary address")
ospf_summ_r1 = {
@@ -1898,24 +1885,24 @@ def test_ospf_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that summary address is flushed from neighbor.")
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB \n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step("Configure summary first & then configure matching static route.")
@@ -1999,7 +1986,7 @@ def test_ospf_type5_summary_tc46_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step(
"Configure External Route summary in R0 to summarise 5"
@@ -2031,20 +2018,20 @@ def test_ospf_type5_summary_tc46_p0(request):
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB.\n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in RIB."
+ "Error: Routes still present in RIB".format(tc_name)
+ )
- step("Verify that show ip ospf summary should show the " "configured summaries.")
+ step("Verify that show ip ospf summary should show the configured summaries.")
input_dict = {
SUMMARY["ipv4"][0]: {
"summaryAddress": SUMMARY["ipv4"][0],
@@ -2055,7 +2042,7 @@ def test_ospf_type5_summary_tc46_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Delete the configured summary")
ospf_summ_r1 = {
@@ -2080,10 +2067,9 @@ def test_ospf_type5_summary_tc46_p0(request):
step("Verify that summary lsa is withdrawn from R1 and deleted from R0.")
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB. \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
@@ -2091,9 +2077,7 @@ def test_ospf_type5_summary_tc46_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary Route still present in RIB".format(
- tc_name
- )
+ ), "Testcase {} : Failed. Error: Summary Route still present in RIB".format(tc_name)
step("show ip ospf summary should not have any summary address.")
input_dict = {
@@ -2109,7 +2093,7 @@ def test_ospf_type5_summary_tc46_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary still present in DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary still present in DB".format(tc_name)
step("Reconfigure summary with no advertise.")
ospf_summ_r1 = {
@@ -2138,20 +2122,20 @@ def test_ospf_type5_summary_tc46_p0(request):
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB. \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB \n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
- step("Verify that show ip ospf summary should show the " "configured summaries.")
+ step("Verify that show ip ospf summary should show the configured summaries.")
input_dict = {
SUMMARY["ipv4"][0]: {
"summaryAddress": SUMMARY["ipv4"][0],
@@ -2162,7 +2146,7 @@ def test_ospf_type5_summary_tc46_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step(
"Change summary address from no advertise to advertise "
@@ -2211,7 +2195,7 @@ def test_ospf_type5_summary_tc46_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -2227,26 +2211,26 @@ def test_ospf_type5_summary_tc46_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv4"], "next_hop": "blackhole"}]}
}
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes is present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in RIB"
+ "Error: Routes is present in RIB".format(tc_name)
+ )
write_test_footer(tc_name)
@@ -2293,11 +2277,9 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
@@ -2325,7 +2307,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -2341,26 +2323,26 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv4"], "next_hop": "blackhole"}]}
}
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n \n Expected: Routes should not be present in OSPF RIB.\n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in RIB.\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step(
"configure route map and add rule to permit configured static "
@@ -2423,7 +2405,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
input_dict = {
SUMMARY["ipv4"][0]: {
@@ -2461,18 +2443,18 @@ def test_ospf_type5_summary_tc47_p0(request):
step("Verify that advertised summary route is flushed from neighbor.")
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB\n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in RIB.\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step("Delete the configured route map.")
@@ -2511,7 +2493,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
input_dict = {
SUMMARY["ipv4"][0]: {
@@ -2526,7 +2508,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Reconfigure the route map with denying configure summary address.")
@@ -2570,7 +2552,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Redistribute static/connected routes without route map.")
@@ -2606,7 +2588,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
input_dict = {
SUMMARY["ipv4"][0]: {
@@ -2621,7 +2603,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step(
"Configure rule to deny all the routes in route map and configure"
@@ -2672,18 +2654,18 @@ def test_ospf_type5_summary_tc47_p0(request):
step("Verify that no summary route is originated.")
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict_summary, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB.\n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict_summary, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in RIB"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
routemaps = {
"r0": {
@@ -2773,7 +2755,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -2789,7 +2771,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Change route map rule for 1 of the routes to deny.")
# Create ip prefix list
@@ -2822,7 +2804,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("add rule in route map to deny configured summary address.")
# Create ip prefix list
@@ -2855,7 +2837,7 @@ def test_ospf_type5_summary_tc47_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
write_test_footer(tc_name)
@@ -2995,7 +2977,7 @@ def test_ospf_type5_summary_tc51_p2(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
write_test_footer(tc_name)
@@ -3042,11 +3024,9 @@ def test_ospf_type5_summary_tc49_p2(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
@@ -3074,7 +3054,7 @@ def test_ospf_type5_summary_tc49_p2(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -3090,26 +3070,26 @@ def test_ospf_type5_summary_tc49_p2(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv4"], "next_hop": "blackhole"}]}
}
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB.\n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in RIB.\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step("Reload the FRR router")
# stop/start -> restart FRR router and verify
@@ -3130,7 +3110,7 @@ def test_ospf_type5_summary_tc49_p2(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -3146,26 +3126,26 @@ def test_ospf_type5_summary_tc49_p2(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv4"], "next_hop": "blackhole"}]}
}
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB. \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in RIB\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step("Kill OSPFd daemon on R0.")
kill_router_daemons(tgen, "r0", ["ospfd"])
@@ -3176,7 +3156,7 @@ def test_ospf_type5_summary_tc49_p2(request):
step("Verify OSPF neighbors are up after bringing back ospfd in R0")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -3194,7 +3174,7 @@ def test_ospf_type5_summary_tc49_p2(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -3210,26 +3190,26 @@ def test_ospf_type5_summary_tc49_p2(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv4"], "next_hop": "blackhole"}]}
}
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed \n Expected: Routes should not be present in OSPF RIB. \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in RIB\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
step("restart zebrad")
kill_router_daemons(tgen, "r0", ["zebra"])
@@ -3251,7 +3231,7 @@ def test_ospf_type5_summary_tc49_p2(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -3267,26 +3247,26 @@ def test_ospf_type5_summary_tc49_p2(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv4"], "next_hop": "blackhole"}]}
}
dut = "r1"
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
- assert (
- result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
- tc_name, result
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in OSPF RIB. \n Error: "
+ "Routes still present in OSPF RIB {}".format(tc_name, result)
)
result = verify_rib(
tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False
)
- assert (
- result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ assert result is not True, (
+ "Testcase {} : Failed\n Expected: Routes should not be present in RIB.\n"
+ "Error: Routes still present in RIB".format(tc_name)
+ )
write_test_footer(tc_name)
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_type7_lsa.py b/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_type7_lsa.py
index cff59c3a40..603aeadb85 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_type7_lsa.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_type7_lsa.py
@@ -132,7 +132,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -235,11 +235,9 @@ def test_ospf_type5_summary_tc44_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r0 = {
"r0": {
@@ -259,9 +257,7 @@ def test_ospf_type5_summary_tc44_p0(request):
"route is sent to R1."
)
- step(
- "Configure summary & redistribute static/connected route with " "metric type 2"
- )
+ step("Configure summary & redistribute static/connected route with metric type 2")
input_dict_summary = {"r0": {"static_routes": [{"network": SUMMARY["ipv4"][3]}]}}
dut = "r1"
@@ -272,7 +268,7 @@ def test_ospf_type5_summary_tc44_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -288,7 +284,7 @@ def test_ospf_type5_summary_tc44_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Learn type 7 lsa from neighbours")
@@ -312,7 +308,7 @@ def test_ospf_type5_summary_tc44_p0(request):
result = verify_rib(tgen, "ipv4", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Routes is missing in RIB".format(tc_name)
ospf_summ_r0 = {
"r0": {
@@ -340,7 +336,7 @@ def test_ospf_type5_summary_tc44_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
step("Verify that already originated summary is intact.")
input_dict = {
@@ -356,7 +352,7 @@ def test_ospf_type5_summary_tc44_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed. Error: Summary missing in OSPF DB".format(tc_name)
dut = "r1"
aggr_timer = {"r1": {"ospf": {"aggr_timer": 6}}}
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_authentication.py b/tests/topotests/ospf_basic_functionality/test_ospf_authentication.py
index 40df0b2308..88219b8400 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_authentication.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_authentication.py
@@ -100,7 +100,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -166,7 +166,7 @@ def test_ospf_authentication_simple_pass_tc28_p1(request):
step("Verify that the neighbour is not FULL between R1 and R2.")
dut = "r1"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut, expected=False)
- assert ospf_covergence is not True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is not True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -192,7 +192,7 @@ def test_ospf_authentication_simple_pass_tc28_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -223,7 +223,7 @@ def test_ospf_authentication_simple_pass_tc28_p1(request):
ospf_covergence = verify_ospf_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=10
)
- assert ospf_covergence is not True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is not True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -245,7 +245,7 @@ def test_ospf_authentication_simple_pass_tc28_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -260,7 +260,7 @@ def test_ospf_authentication_simple_pass_tc28_p1(request):
"show ip ospf neighbor cmd."
)
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut, expected=False)
- assert ospf_covergence is not True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is not True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -274,7 +274,7 @@ def test_ospf_authentication_simple_pass_tc28_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -314,7 +314,7 @@ def test_ospf_authentication_simple_pass_tc28_p1(request):
dut = "r1"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -361,7 +361,7 @@ def test_ospf_authentication_md5_tc29_p1(request):
ospf_covergence = verify_ospf_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=6
)
- assert ospf_covergence is not True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is not True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -393,7 +393,7 @@ def test_ospf_authentication_md5_tc29_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -426,7 +426,7 @@ def test_ospf_authentication_md5_tc29_p1(request):
ospf_covergence = verify_ospf_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=10
)
- assert ospf_covergence is not True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is not True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -454,7 +454,7 @@ def test_ospf_authentication_md5_tc29_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -469,7 +469,7 @@ def test_ospf_authentication_md5_tc29_p1(request):
"show ip ospf neighbor cmd."
)
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut, expected=False)
- assert ospf_covergence is not True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is not True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -483,7 +483,7 @@ def test_ospf_authentication_md5_tc29_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -528,7 +528,7 @@ def test_ospf_authentication_md5_tc29_p1(request):
dut = "r1"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -576,7 +576,7 @@ def test_ospf_authentication_different_auths_tc30_p1(request):
ospf_covergence = verify_ospf_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=10
)
- assert ospf_covergence is not True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is not True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -608,7 +608,7 @@ def test_ospf_authentication_different_auths_tc30_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -655,7 +655,7 @@ def test_ospf_authentication_different_auths_tc30_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -687,7 +687,7 @@ def test_ospf_authentication_different_auths_tc30_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -720,7 +720,7 @@ def test_ospf_authentication_different_auths_tc30_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -765,7 +765,7 @@ def test_ospf_authentication_different_auths_tc30_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -810,7 +810,7 @@ def test_ospf_authentication_different_auths_tc30_p1(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_chaos.py b/tests/topotests/ospf_basic_functionality/test_ospf_chaos.py
index 124b36a5fa..e58f081f96 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_chaos.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_chaos.py
@@ -111,7 +111,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -177,7 +177,7 @@ def test_ospf_chaos_tc31_p1(request):
step("Verify OSPF neighbors after base config is done.")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -198,7 +198,7 @@ def test_ospf_chaos_tc31_p1(request):
dut = "r0"
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut, expected=False)
- assert ospf_covergence is not True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is not True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -208,7 +208,7 @@ def test_ospf_chaos_tc31_p1(request):
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -217,7 +217,7 @@ def test_ospf_chaos_tc31_p1(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
@@ -227,7 +227,7 @@ def test_ospf_chaos_tc31_p1(request):
step("Verify OSPF neighbors are up after bringing back ospfd in R0")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -250,7 +250,7 @@ def test_ospf_chaos_tc31_p1(request):
dut = "r1"
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut, expected=False)
- assert ospf_covergence is not True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is not True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -260,7 +260,7 @@ def test_ospf_chaos_tc31_p1(request):
step("Verify OSPF neighbors are up after bringing back ospfd in R1")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -316,7 +316,7 @@ def test_ospf_chaos_tc32_p1(request):
step("Verify OSPF neighbors after base config is done.")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -338,7 +338,7 @@ def test_ospf_chaos_tc32_p1(request):
step("Verify OSPF neighbors are up after restarting R0")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -361,7 +361,7 @@ def test_ospf_chaos_tc32_p1(request):
step("Verify OSPF neighbors are up after restarting R1")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -421,7 +421,7 @@ def test_ospf_chaos_tc34_p1(request):
step("Verify OSPF neighbors after base config is done.")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -444,7 +444,7 @@ def test_ospf_chaos_tc34_p1(request):
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -453,7 +453,7 @@ def test_ospf_chaos_tc34_p1(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
@@ -463,7 +463,7 @@ def test_ospf_chaos_tc34_p1(request):
step("Verify OSPF neighbors are up after bringing back ospfd in R0")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -488,7 +488,7 @@ def test_ospf_chaos_tc34_p1(request):
step("Verify OSPF neighbors are up after bringing back ospfd in R1")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_ecmp.py b/tests/topotests/ospf_basic_functionality/test_ospf_ecmp.py
index d58e2503ed..aba313db9f 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_ecmp.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_ecmp.py
@@ -114,7 +114,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -168,7 +168,7 @@ def test_ospf_ecmp_tc16_p0(request):
step("Verify that OSPF is up with 8 neighborship sessions.")
dut = "r1"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -217,7 +217,7 @@ def test_ospf_ecmp_tc16_p0(request):
result = verify_ospf_rib(tgen, dut, input_dict, next_hop=nh, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -227,7 +227,7 @@ def test_ospf_ecmp_tc16_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
@@ -259,7 +259,7 @@ def test_ospf_ecmp_tc16_p0(request):
step("Verify that OSPF is up with 8 neighborship sessions.")
dut = "r1"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -286,7 +286,7 @@ def test_ospf_ecmp_tc16_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -303,7 +303,7 @@ def test_ospf_ecmp_tc16_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
@@ -343,7 +343,7 @@ def test_ospf_ecmp_tc17_p0(request):
step("Verify that OSPF is up with 2 neighborship sessions.")
dut = "r1"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -394,7 +394,7 @@ def test_ospf_ecmp_tc17_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -411,7 +411,7 @@ def test_ospf_ecmp_tc17_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_ecmp_lan.py b/tests/topotests/ospf_basic_functionality/test_ospf_ecmp_lan.py
index 6a565571be..1eeb23e9f7 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_ecmp_lan.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_ecmp_lan.py
@@ -115,7 +115,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -169,7 +169,7 @@ def test_ospf_lan_ecmp_tc18_p0(request):
step("Verify that OSPF is up with 8 neighborship sessions.")
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -222,7 +222,7 @@ def test_ospf_lan_ecmp_tc18_p0(request):
dut = "r0"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -231,7 +231,7 @@ def test_ospf_lan_ecmp_tc18_p0(request):
dut = "r2"
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -261,7 +261,7 @@ def test_ospf_lan_ecmp_tc18_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -278,7 +278,7 @@ def test_ospf_lan_ecmp_tc18_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_lan.py b/tests/topotests/ospf_basic_functionality/test_ospf_lan.py
index 53b1be6d71..1358027f21 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_lan.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_lan.py
@@ -114,7 +114,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -165,9 +165,9 @@ def test_ospf_lan_tc1_p0(request):
"r0": {
"ospf": {
"neighbors": {
- "r1": {"state": "Full", "role": "DR"},
- "r2": {"state": "Full", "role": "DROther"},
- "r3": {"state": "Full", "role": "DROther"},
+ "r1": {"nbrState": "Full", "role": "DR"},
+ "r2": {"nbrState": "Full", "role": "DROther"},
+ "r3": {"nbrState": "Full", "role": "DROther"},
}
}
}
@@ -185,9 +185,9 @@ def test_ospf_lan_tc1_p0(request):
"r1": {
"ospf": {
"neighbors": {
- "r0": {"state": "Full", "role": "Backup"},
- "r2": {"state": "Full", "role": "DROther"},
- "r3": {"state": "Full", "role": "DROther"},
+ "r0": {"nbrState": "Full", "role": "Backup"},
+ "r2": {"nbrState": "Full", "role": "DROther"},
+ "r3": {"nbrState": "Full", "role": "DROther"},
}
}
}
@@ -197,7 +197,8 @@ def test_ospf_lan_tc1_p0(request):
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
step(
- "Configure DR pririty 100 on R0 and clear ospf neighbors " "on all the routers."
+ "Configure DR priority 100 on R0 and clear ospf neighbors "
+ "on all the routers."
)
input_dict = {
@@ -223,9 +224,9 @@ def test_ospf_lan_tc1_p0(request):
"r0": {
"ospf": {
"neighbors": {
- "r1": {"state": "Full", "role": "Backup"},
- "r2": {"state": "Full", "role": "DROther"},
- "r3": {"state": "Full", "role": "DROther"},
+ "r1": {"nbrState": "Full", "role": "Backup"},
+ "r2": {"nbrState": "Full", "role": "DROther"},
+ "r3": {"nbrState": "Full", "role": "DROther"},
}
}
}
@@ -235,7 +236,8 @@ def test_ospf_lan_tc1_p0(request):
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
step(
- "Configure DR pririty 150 on R0 and clear ospf neighbors " "on all the routers."
+ "Configure DR priority 150 on R0 and clear ospf neighbors "
+ "on all the routers."
)
input_dict = {
@@ -261,9 +263,9 @@ def test_ospf_lan_tc1_p0(request):
"r0": {
"ospf": {
"neighbors": {
- "r1": {"state": "Full", "role": "Backup"},
- "r2": {"state": "Full", "role": "DROther"},
- "r3": {"state": "Full", "role": "DROther"},
+ "r1": {"nbrState": "Full", "role": "Backup"},
+ "r2": {"nbrState": "Full", "role": "DROther"},
+ "r3": {"nbrState": "Full", "role": "DROther"},
}
}
}
@@ -297,9 +299,9 @@ def test_ospf_lan_tc1_p0(request):
"r0": {
"ospf": {
"neighbors": {
- "r1": {"state": "Full", "role": "DR"},
- "r2": {"state": "2-Way", "role": "DROther"},
- "r3": {"state": "2-Way", "role": "DROther"},
+ "r1": {"nbrState": "Full", "role": "DR"},
+ "r2": {"nbrState": "2-Way", "role": "DROther"},
+ "r3": {"nbrState": "2-Way", "role": "DROther"},
}
}
}
@@ -336,9 +338,9 @@ def test_ospf_lan_tc1_p0(request):
"r0": {
"ospf": {
"neighbors": {
- "r1": {"state": "Full", "role": "Backup"},
- "r2": {"state": "Full", "role": "DROther"},
- "r3": {"state": "Full", "role": "DROther"},
+ "r1": {"nbrState": "Full", "role": "Backup"},
+ "r2": {"nbrState": "Full", "role": "DROther"},
+ "r3": {"nbrState": "Full", "role": "DROther"},
}
}
}
@@ -355,7 +357,7 @@ def test_ospf_lan_tc1_p0(request):
result = verify_ospf_neighbor(tgen, topo, dut, lan=True, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r0: OSPF neighbors-hip is up \n Error: {}".format(
+ ), "Testcase {} : Failed \n r0: OSPF neighbors-hip is up \n Error: {}".format(
tc_name, result
)
@@ -368,9 +370,9 @@ def test_ospf_lan_tc1_p0(request):
"r0": {
"ospf": {
"neighbors": {
- "r1": {"state": "Full", "role": "DR"},
- "r2": {"state": "Full", "role": "DROther"},
- "r3": {"state": "Full", "role": "DROther"},
+ "r1": {"nbrState": "Full", "role": "DR"},
+ "r2": {"nbrState": "Full", "role": "DROther"},
+ "r3": {"nbrState": "Full", "role": "DROther"},
}
}
}
@@ -423,9 +425,9 @@ def test_ospf_lan_tc1_p0(request):
"r1": {
"ospf": {
"neighbors": {
- "r0": {"state": "Full", "role": "Backup"},
- "r2": {"state": "Full", "role": "DROther"},
- "r3": {"state": "Full", "role": "DROther"},
+ "r0": {"nbrState": "Full", "role": "Backup"},
+ "r2": {"nbrState": "Full", "role": "DROther"},
+ "r3": {"nbrState": "Full", "role": "DROther"},
}
}
}
@@ -449,9 +451,9 @@ def test_ospf_lan_tc1_p0(request):
"r1": {
"ospf": {
"neighbors": {
- "r0": {"state": "Full", "role": "Backup"},
- "r2": {"state": "Full", "role": "DROther"},
- "r3": {"state": "Full", "role": "DROther"},
+ "r0": {"nbrState": "Full", "role": "Backup"},
+ "r2": {"nbrState": "Full", "role": "DROther"},
+ "r3": {"nbrState": "Full", "role": "DROther"},
}
}
}
@@ -491,7 +493,7 @@ def test_ospf_lan_tc2_p0(request):
"s1": {
"ospf": {
"priority": 98,
- "timerDeadSecs": 4,
+ "timerDeadSecs": 10,
"area": "0.0.0.3",
"mcastMemberOspfDesignatedRouters": True,
"mcastMemberOspfAllRouters": True,
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_nssa.py b/tests/topotests/ospf_basic_functionality/test_ospf_nssa.py
index 0c4697cc21..d669e21d4d 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_nssa.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_nssa.py
@@ -112,7 +112,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -220,11 +220,11 @@ def test_ospf_learning_tc15_p0(request):
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
- step("Change area 1 as non nssa area (on the fly changing area" " type on DUT).")
+ step("Change area 1 as non nssa area (on the fly changing area type on DUT).")
for rtr in ["r1", "r2", "r3"]:
input_dict = {
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_p2mp.py b/tests/topotests/ospf_basic_functionality/test_ospf_p2mp.py
index 858412f1d3..4f797743e7 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_p2mp.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_p2mp.py
@@ -411,17 +411,17 @@ def test_ospf_nbrs(tgen):
"neighbors": {
"100.1.1.1": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
"100.1.1.2": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
"100.1.1.3": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
}
@@ -434,17 +434,17 @@ def test_ospf_nbrs(tgen):
"neighbors": {
"100.1.1.0": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
"100.1.1.2": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
"100.1.1.3": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
}
@@ -457,17 +457,17 @@ def test_ospf_nbrs(tgen):
"neighbors": {
"100.1.1.0": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
"100.1.1.1": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
"100.1.1.3": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
}
@@ -480,17 +480,17 @@ def test_ospf_nbrs(tgen):
"neighbors": {
"100.1.1.0": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
"100.1.1.1": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
"100.1.1.2": [
{
- "state": "Full/DROther",
+ "nbrState": "Full/DROther",
}
],
}
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_routemaps.py b/tests/topotests/ospf_basic_functionality/test_ospf_routemaps.py
index dad6d915e8..c9f43cdfe4 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_routemaps.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_routemaps.py
@@ -127,7 +127,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -201,9 +201,7 @@ def test_ospf_routemaps_functionality_tc19_p0(request):
redistribute_ospf(tgen, topo, "r0", "static", delete=True)
- step(
- "Create prefix-list in R0 to permit 10.0.20.1/32 prefix &" " deny 10.0.20.2/32"
- )
+ step("Create prefix-list in R0 to permit 10.0.20.1/32 prefix & deny 10.0.20.2/32")
# Create ip prefix list
pfx_list = {
@@ -294,7 +292,7 @@ def test_ospf_routemaps_functionality_tc19_p0(request):
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -303,7 +301,7 @@ def test_ospf_routemaps_functionality_tc19_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are present in fib \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are present in fib \n Error: {}".format(
tc_name, result
)
@@ -347,7 +345,7 @@ def test_ospf_routemaps_functionality_tc19_p0(request):
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -356,7 +354,7 @@ def test_ospf_routemaps_functionality_tc19_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -404,7 +402,7 @@ def test_ospf_routemaps_functionality_tc19_p0(request):
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -413,7 +411,7 @@ def test_ospf_routemaps_functionality_tc19_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
@@ -464,7 +462,7 @@ def test_ospf_routemaps_functionality_tc20_p0(request):
result = verify_ospf_rib(tgen, dut, input_dict, retry_timeout=4, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -479,7 +477,7 @@ def test_ospf_routemaps_functionality_tc20_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
@@ -499,7 +497,7 @@ def test_ospf_routemaps_functionality_tc20_p0(request):
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -508,7 +506,7 @@ def test_ospf_routemaps_functionality_tc20_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
@@ -523,7 +521,7 @@ def test_ospf_routemaps_functionality_tc20_p0(request):
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -532,7 +530,7 @@ def test_ospf_routemaps_functionality_tc20_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
@@ -553,7 +551,7 @@ def test_ospf_routemaps_functionality_tc20_p0(request):
result = verify_ospf_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: OSPF routes are present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: OSPF routes are present \n Error: {}".format(
tc_name, result
)
@@ -562,7 +560,7 @@ def test_ospf_routemaps_functionality_tc20_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n " "r1: routes are still present \n Error: {}".format(
+ ), "Testcase {} : Failed \n r1: routes are still present \n Error: {}".format(
tc_name, result
)
@@ -861,7 +859,7 @@ def test_ospf_routemaps_functionality_tc24_p0(request):
result = verify_prefix_lists(tgen, pfx_list)
assert (
result is not True
- ), "Testcase {} : Failed \n Prefix list not " "present. Error: {}".format(
+ ), "Testcase {} : Failed \n Prefix list not present. Error: {}".format(
tc_name, result
)
@@ -930,7 +928,7 @@ def test_ospf_routemaps_functionality_tc24_p0(request):
result = verify_prefix_lists(tgen, pfx_list)
assert (
result is not True
- ), "Testcase {} : Failed \n Prefix list not " "present. Error: {}".format(
+ ), "Testcase {} : Failed \n Prefix list not present. Error: {}".format(
tc_name, result
)
@@ -1078,7 +1076,7 @@ def test_ospf_routemaps_functionality_tc25_p0(request):
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_rte_calc.py b/tests/topotests/ospf_basic_functionality/test_ospf_rte_calc.py
index 63c421ec84..f0950a2db3 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_rte_calc.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_rte_calc.py
@@ -123,7 +123,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -171,7 +171,7 @@ def test_ospf_redistribution_tc5_p0(request):
step("Verify that OSPF neighbors are FULL.")
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -295,7 +295,7 @@ def test_ospf_redistribution_tc6_p0(request):
step("Verify that OSPF neighbors are FULL.")
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -524,7 +524,7 @@ def test_ospf_redistribution_tc8_p1(request):
step("Verify that OSPF neighbours are reset and forms new adjacencies.")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -558,7 +558,7 @@ def test_ospf_rfc2328_appendinxE_p0(request):
step("Verify that OSPF neighbours are Full.")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_single_area.py b/tests/topotests/ospf_basic_functionality/test_ospf_single_area.py
index 39bbab42e7..757d6fb1d5 100644
--- a/tests/topotests/ospf_basic_functionality/test_ospf_single_area.py
+++ b/tests/topotests/ospf_basic_functionality/test_ospf_single_area.py
@@ -108,7 +108,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error {}".format(
ospf_covergence
)
@@ -358,7 +358,7 @@ def test_ospf_p2p_tc3_p0(request):
# Api call verify whether BGP is converged
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -440,7 +440,7 @@ def test_ospf_hello_tc10_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -486,7 +486,7 @@ def test_ospf_hello_tc10_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -532,7 +532,7 @@ def test_ospf_hello_tc10_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -575,7 +575,7 @@ def test_ospf_hello_tc10_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -597,7 +597,7 @@ def test_ospf_show_p1(request):
reset_config_on_routers(tgen)
ospf_covergence = verify_ospf_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
dut = "r1"
@@ -690,7 +690,7 @@ def test_ospf_dead_tc11_p0(request):
result = verify_ospf_interface(tgen, topo, dut=dut, input_dict=input_dict)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step("modify dead interval from default value to r1" "dead interval timer on r2")
+ step("modify dead interval from default value to r1 dead interval timer on r2")
topo1 = {
"r0": {
@@ -714,11 +714,11 @@ def test_ospf_dead_tc11_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
- step("reconfigure the default dead interval timer value to " "default on r1 and r2")
+ step("reconfigure the default dead interval timer value to default on r1 and r2")
topo1 = {
"r0": {
"links": {
@@ -755,7 +755,7 @@ def test_ospf_dead_tc11_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -797,7 +797,7 @@ def test_ospf_dead_tc11_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error {}".format(
ospf_covergence
)
@@ -835,9 +835,7 @@ def test_ospf_dead_tc11_p0(request):
result = create_interfaces_cfg(tgen, topo1)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step(
- "Verify that timer value is deleted from intf & " "set to default value 40 sec."
- )
+ step("Verify that timer value is deleted from intf & set to default value 40 sec.")
input_dict = {"r1": {"links": {"r0": {"ospf": {"timerDeadSecs": 40}}}}}
dut = "r1"
result = verify_ospf_interface(tgen, topo, dut=dut, input_dict=input_dict)
@@ -883,18 +881,14 @@ def test_ospf_tc4_mtu_ignore_p0(request):
clear_ospf(tgen, "r0")
- step(
- "Verify that OSPF neighborship between R0 and R1 is stuck in Exstart" " State."
- )
+ step("Verify that OSPF neighborship between R0 and R1 is stuck in Exstart State.")
result = verify_ospf_neighbor(tgen, topo, expected=False)
assert result is not True, (
"Testcase {} : Failed \n OSPF nbrs are Full "
"instead of Exstart. Error: {}".format(tc_name, result)
)
- step(
- "Verify that configured MTU value is updated in the show ip " "ospf interface."
- )
+ step("Verify that configured MTU value is updated in the show ip ospf interface.")
dut = "r0"
input_dict = {"r0": {"links": {"r1": {"ospf": {"mtuBytes": 1200}}}}}
@@ -951,9 +945,7 @@ def test_ospf_tc4_mtu_ignore_p0(request):
clear_ospf(tgen, "r0")
- step(
- "Verify that OSPF neighborship between R0 and R1 is stuck in Exstart" " State."
- )
+ step("Verify that OSPF neighborship between R0 and R1 is stuck in Exstart State.")
result = verify_ospf_neighbor(tgen, topo, expected=False)
assert result is not True, (
"Testcase {} : Failed \n OSPF nbrs are Full "
@@ -970,9 +962,7 @@ def test_ospf_tc4_mtu_ignore_p0(request):
result = verify_ospf_neighbor(tgen, topo)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step(
- "Configure ospf interface with jumbo MTU (9216)." "Reset ospf neighbors on R0."
- )
+ step("Configure ospf interface with jumbo MTU (9216). Reset ospf neighbors on R0.")
rtr0.run("ip link set {} mtu 9216".format(r0_r1_intf))
rtr1.run("ip link set {} mtu 9216".format(r1_r0_intf))
diff --git a/tests/topotests/ospf_gr_helper/test_ospf_gr_helper1.py b/tests/topotests/ospf_gr_helper/test_ospf_gr_helper1.py
index 1c26596230..79374281cb 100644
--- a/tests/topotests/ospf_gr_helper/test_ospf_gr_helper1.py
+++ b/tests/topotests/ospf_gr_helper/test_ospf_gr_helper1.py
@@ -119,7 +119,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf_covergence
)
@@ -182,20 +182,20 @@ def test_ospf_gr_helper_tc1_p0(request):
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
assert (
ospf_covergence is True
- ), "OSPF is not after reset config \n Error:" " {}".format(ospf_covergence)
+ ), "OSPF is not after reset config \n Error: {}".format(ospf_covergence)
- step("Verify that GR helper route is disabled by default to the in" "the DUT.")
+ step("Verify that GR helper route is disabled by default to the in the DUT.")
input_dict = {
"helperSupport": "Disabled",
"strictLsaCheck": "Enabled",
- "restartSupoort": "Planned and Unplanned Restarts",
+ "restartSupport": "Planned and Unplanned Restarts",
"supportedGracePeriod": 1800,
}
dut = "r0"
result = verify_ospf_gr_helper(tgen, topo, dut, input_dict)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step("Verify that DUT does not enter helper mode upon receiving the " "grace lsa.")
+ step("Verify that DUT does not enter helper mode upon receiving the grace lsa.")
# send grace lsa
scapy_send_raw_packet(tgen, topo, "r1", intf1, pkt)
@@ -205,7 +205,7 @@ def test_ospf_gr_helper_tc1_p0(request):
result = verify_ospf_gr_helper(tgen, topo, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed. DUT entered helper role " " \n Error: {}".format(
+ ), "Testcase {} : Failed. DUT entered helper role \n Error: {}".format(
tc_name, result
)
@@ -220,7 +220,7 @@ def test_ospf_gr_helper_tc1_p0(request):
input_dict = {
"helperSupport": "Enabled",
"strictLsaCheck": "Enabled",
- "restartSupoort": "Planned and Unplanned Restarts",
+ "restartSupport": "Planned and Unplanned Restarts",
"supportedGracePeriod": 1800,
}
dut = "r0"
@@ -234,7 +234,7 @@ def test_ospf_gr_helper_tc1_p0(request):
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
step("Perform GR in RR.")
- step("Verify that DUT does enter helper mode upon receiving" " the grace lsa.")
+ step("Verify that DUT does enter helper mode upon receiving the grace lsa.")
input_dict = {"activeRestarterCnt": 1}
gracelsa_sent = False
repeat = 0
@@ -277,7 +277,7 @@ def test_ospf_gr_helper_tc1_p0(request):
result = create_router_ospf(tgen, topo, ospf_gr_r0)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step("Verify that DUT does enter helper mode upon receiving" " the grace lsa.")
+ step("Verify that DUT does enter helper mode upon receiving the grace lsa.")
input_dict = {"activeRestarterCnt": 1}
gracelsa_sent = False
repeat = 0
@@ -306,7 +306,7 @@ def test_ospf_gr_helper_tc1_p0(request):
result = create_router_ospf(tgen, topo, ospf_gr_r0)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step("Verify that GR helper router is disabled in the DUT for" " router id x.x.x.x")
+ step("Verify that GR helper router is disabled in the DUT for router id x.x.x.x")
input_dict = {"enabledRouterIds": [{"routerId": "1.1.1.1"}]}
dut = "r0"
result = verify_ospf_gr_helper(tgen, topo, dut, input_dict, expected=False)
@@ -343,7 +343,7 @@ def test_ospf_gr_helper_tc2_p0(request):
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
assert (
ospf_covergence is True
- ), "OSPF is not after reset config \n Error:" " {}".format(ospf_covergence)
+ ), "OSPF is not after reset config \n Error: {}".format(ospf_covergence)
ospf_gr_r0 = {
"r0": {"ospf": {"graceful-restart": {"helper enable": [], "opaque": True}}}
}
diff --git a/tests/topotests/ospf_gr_helper/test_ospf_gr_helper2.py b/tests/topotests/ospf_gr_helper/test_ospf_gr_helper2.py
index a3ccb58d38..46c0da309f 100644
--- a/tests/topotests/ospf_gr_helper/test_ospf_gr_helper2.py
+++ b/tests/topotests/ospf_gr_helper/test_ospf_gr_helper2.py
@@ -119,7 +119,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf_covergence
)
@@ -188,10 +188,10 @@ def test_ospf_gr_helper_tc3_p1(request):
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
assert (
ospf_covergence is True
- ), "OSPF is not after reset config \n Error:" " {}".format(ospf_covergence)
- step(
- "Configure DR pririty 100 on R0 and clear ospf neighbors " "on all the routers."
- )
+ ), "OSPF is not after reset config \n Error: {}".format(ospf_covergence)
+
+ step("Configure DR priority 100 on R0 and clear ospf neighbors "
+ "on all the routers.")
input_dict = {
"r0": {
@@ -216,9 +216,9 @@ def test_ospf_gr_helper_tc3_p1(request):
"r0": {
"ospf": {
"neighbors": {
- "r1": {"state": "Full", "role": "Backup"},
- "r2": {"state": "Full", "role": "DROther"},
- "r3": {"state": "Full", "role": "DROther"},
+ "r1": {"nbrState": "Full", "role": "Backup"},
+ "r2": {"nbrState": "Full", "role": "DROther"},
+ "r3": {"nbrState": "Full", "role": "DROther"},
}
}
}
@@ -282,10 +282,8 @@ def test_ospf_gr_helper_tc4_p1(request):
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
assert (
ospf_covergence is True
- ), "OSPF is not after reset config \n Error:" " {}".format(ospf_covergence)
- step(
- "Configure DR pririty 100 on R0 and clear ospf neighbors " "on all the routers."
- )
+ ), "OSPF is not after reset config \n Error: {}".format(ospf_covergence)
+ step("Configure DR priority 0 on R0 and clear ospf neighbors on all the routers.")
input_dict = {
"r0": {
@@ -310,9 +308,9 @@ def test_ospf_gr_helper_tc4_p1(request):
"r0": {
"ospf": {
"neighbors": {
- "r1": {"state": "Full", "role": "DR"},
- "r2": {"state": "2-Way", "role": "DROther"},
- "r3": {"state": "2-Way", "role": "DROther"},
+ "r1": {"nbrState": "Full", "role": "DR"},
+ "r2": {"nbrState": "2-Way", "role": "DROther"},
+ "r3": {"nbrState": "2-Way", "role": "DROther"},
}
}
}
diff --git a/tests/topotests/ospf_gr_helper/test_ospf_gr_helper3.py b/tests/topotests/ospf_gr_helper/test_ospf_gr_helper3.py
index 64aac2fba8..3be28196d8 100644
--- a/tests/topotests/ospf_gr_helper/test_ospf_gr_helper3.py
+++ b/tests/topotests/ospf_gr_helper/test_ospf_gr_helper3.py
@@ -119,7 +119,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf_covergence
)
@@ -193,7 +193,7 @@ def test_ospf_gr_helper_tc7_p1(request):
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
assert (
ospf_covergence is True
- ), "OSPF is not after reset config \n Error:" " {}".format(ospf_covergence)
+ ), "OSPF is not after reset config \n Error: {}".format(ospf_covergence)
ospf_gr_r0 = {
"r0": {"ospf": {"graceful-restart": {"helper enable": [], "opaque": True}}}
}
@@ -221,7 +221,7 @@ def test_ospf_gr_helper_tc7_p1(request):
result = verify_ospf_gr_helper(tgen, topo, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed. DUT entered helper role " " \n Error: {}".format(
+ ), "Testcase {} : Failed. DUT entered helper role \n Error: {}".format(
tc_name, result
)
@@ -253,7 +253,7 @@ def test_ospf_gr_helper_tc8_p1(request):
ospf_covergence = verify_ospf_neighbor(tgen, topo, lan=True)
assert (
ospf_covergence is True
- ), "OSPF is not after reset config \n Error:" " {}".format(ospf_covergence)
+ ), "OSPF is not after reset config \n Error: {}".format(ospf_covergence)
ospf_gr_r0 = {
"r0": {"ospf": {"graceful-restart": {"helper enable": [], "opaque": True}}}
}
diff --git a/tests/topotests/ospf_gr_topo1/rt1/show_ip_ospf_neighbor.json b/tests/topotests/ospf_gr_topo1/rt1/show_ip_ospf_neighbor.json
index f82758101c..0212f9da9c 100644
--- a/tests/topotests/ospf_gr_topo1/rt1/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ospf_gr_topo1/rt1/show_ip_ospf_neighbor.json
@@ -3,7 +3,7 @@
"2.2.2.2":[
{
"converged":"Full",
- "address":"10.0.1.2",
+ "ifaceAddress":"10.0.1.2",
"ifaceName":"eth-rt2:10.0.1.1"
}
]
diff --git a/tests/topotests/ospf_gr_topo1/rt2/show_ip_ospf_neighbor.json b/tests/topotests/ospf_gr_topo1/rt2/show_ip_ospf_neighbor.json
index 5a0b092702..3114660483 100644
--- a/tests/topotests/ospf_gr_topo1/rt2/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ospf_gr_topo1/rt2/show_ip_ospf_neighbor.json
@@ -3,14 +3,14 @@
"1.1.1.1":[
{
"converged":"Full",
- "address":"10.0.1.1",
+ "ifaceAddress":"10.0.1.1",
"ifaceName":"eth-rt1:10.0.1.2"
}
],
"3.3.3.3":[
{
"converged":"Full",
- "address":"10.0.2.3",
+ "ifaceAddress":"10.0.2.3",
"ifaceName":"eth-rt3:10.0.2.2"
}
]
diff --git a/tests/topotests/ospf_gr_topo1/rt3/show_ip_ospf_neighbor.json b/tests/topotests/ospf_gr_topo1/rt3/show_ip_ospf_neighbor.json
index ab5e78414d..49a019d36d 100644
--- a/tests/topotests/ospf_gr_topo1/rt3/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ospf_gr_topo1/rt3/show_ip_ospf_neighbor.json
@@ -3,21 +3,21 @@
"2.2.2.2":[
{
"converged":"Full",
- "address":"10.0.2.2",
+ "ifaceAddress":"10.0.2.2",
"ifaceName":"eth-rt2:10.0.2.3"
}
],
"4.4.4.4":[
{
"converged":"Full",
- "address":"10.0.3.4",
+ "ifaceAddress":"10.0.3.4",
"ifaceName":"eth-rt4:10.0.3.3"
}
],
"6.6.6.6":[
{
"converged":"Full",
- "address":"10.0.4.6",
+ "ifaceAddress":"10.0.4.6",
"ifaceName":"eth-rt6:10.0.4.3"
}
]
diff --git a/tests/topotests/ospf_gr_topo1/rt4/show_ip_ospf_neighbor.json b/tests/topotests/ospf_gr_topo1/rt4/show_ip_ospf_neighbor.json
index 405679c10e..9ab49d7266 100644
--- a/tests/topotests/ospf_gr_topo1/rt4/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ospf_gr_topo1/rt4/show_ip_ospf_neighbor.json
@@ -3,14 +3,14 @@
"3.3.3.3":[
{
"converged":"Full",
- "address":"10.0.3.3",
+ "ifaceAddress":"10.0.3.3",
"ifaceName":"eth-rt3:10.0.3.4"
}
],
"5.5.5.5":[
{
"converged":"Full",
- "address":"10.0.5.5",
+ "ifaceAddress":"10.0.5.5",
"ifaceName":"eth-rt5:10.0.5.4"
}
]
diff --git a/tests/topotests/ospf_gr_topo1/rt5/show_ip_ospf_neighbor.json b/tests/topotests/ospf_gr_topo1/rt5/show_ip_ospf_neighbor.json
index 893d454368..7d3d589772 100644
--- a/tests/topotests/ospf_gr_topo1/rt5/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ospf_gr_topo1/rt5/show_ip_ospf_neighbor.json
@@ -3,7 +3,7 @@
"4.4.4.4":[
{
"converged":"Full",
- "address":"10.0.5.4",
+ "ifaceAddress":"10.0.5.4",
"ifaceName":"eth-rt4:10.0.5.5"
}
]
diff --git a/tests/topotests/ospf_gr_topo1/rt6/show_ip_ospf_neighbor.json b/tests/topotests/ospf_gr_topo1/rt6/show_ip_ospf_neighbor.json
index 564a513ac6..506eb4086b 100644
--- a/tests/topotests/ospf_gr_topo1/rt6/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ospf_gr_topo1/rt6/show_ip_ospf_neighbor.json
@@ -3,14 +3,14 @@
"3.3.3.3":[
{
"converged":"Full",
- "address":"10.0.4.3",
+ "ifaceAddress":"10.0.4.3",
"ifaceName":"eth-rt3:10.0.4.6"
}
],
"7.7.7.7":[
{
"converged":"Full",
- "address":"10.0.6.7",
+ "ifaceAddress":"10.0.6.7",
"ifaceName":"eth-rt7:10.0.6.6"
}
]
diff --git a/tests/topotests/ospf_gr_topo1/rt7/show_ip_ospf_neighbor.json b/tests/topotests/ospf_gr_topo1/rt7/show_ip_ospf_neighbor.json
index bc6b60697c..6429148004 100644
--- a/tests/topotests/ospf_gr_topo1/rt7/show_ip_ospf_neighbor.json
+++ b/tests/topotests/ospf_gr_topo1/rt7/show_ip_ospf_neighbor.json
@@ -3,7 +3,7 @@
"6.6.6.6":[
{
"converged":"Full",
- "address":"10.0.6.6",
+ "ifaceAddress":"10.0.6.6",
"ifaceName":"eth-rt6:10.0.6.7"
}
]
diff --git a/tests/topotests/ospf_metric_propagation/__init__.py b/tests/topotests/ospf_metric_propagation/__init__.py
new file mode 100755
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/__init__.py
diff --git a/tests/topotests/ospf_metric_propagation/h1/frr.conf b/tests/topotests/ospf_metric_propagation/h1/frr.conf
new file mode 100644
index 0000000000..1196a192dd
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/h1/frr.conf
@@ -0,0 +1,10 @@
+!
+hostname h1
+password zebra
+log file /tmp/h1-frr.log
+!
+ip route 0.0.0.0/0 10.0.91.1
+!
+interface h1-eth0
+ ip address 10.0.91.2/24
+! \ No newline at end of file
diff --git a/tests/topotests/ospf_metric_propagation/h2/frr.conf b/tests/topotests/ospf_metric_propagation/h2/frr.conf
new file mode 100644
index 0000000000..f951fe6ba1
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/h2/frr.conf
@@ -0,0 +1,10 @@
+!
+hostname h2
+password zebra
+log file /tmp/h2-frr.log
+!
+ip route 0.0.0.0/0 10.0.94.4
+!
+interface h2-eth0
+ ip address 10.0.94.2/24
+! \ No newline at end of file
diff --git a/tests/topotests/ospf_metric_propagation/r1/frr.conf b/tests/topotests/ospf_metric_propagation/r1/frr.conf
new file mode 100644
index 0000000000..85230494dd
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/r1/frr.conf
@@ -0,0 +1,96 @@
+!
+hostname r1
+password zebra
+log file /tmp/r1-frr.log
+ip forwarding
+!
+interface r1-eth0
+ ip address 10.0.1.1/24
+ ip ospf cost 100
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r1-eth1 vrf blue
+ ip address 10.0.10.1/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+interface r1-eth2 vrf green
+ ip address 10.0.91.1/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+!
+router ospf
+ ospf router-id 10.0.255.1
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.1.0/24 area 0
+!
+router ospf vrf blue
+ ospf router-id 10.0.255.1
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.10.0/24 area 0
+!
+router ospf vrf green
+ ospf router-id 10.0.255.1
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.91.0/24 area 0
+!
+router bgp 99
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf route-map rmap
+ import vrf route-map rmap
+ import vrf blue
+ import vrf green
+ !
+!
+router bgp 99 vrf blue
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf route-map rmap
+ import vrf route-map rmap
+ import vrf default
+ import vrf green
+ !
+router bgp 99 vrf green
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf
+ import vrf route-map rmap
+ import vrf default
+ import vrf blue
+ !
+!
+route-map rmap permit 10
+ set metric-type type-1
+ set metric +1
+ exit
+!
+ip prefix-list min seq 5 permit 10.0.80.0/24
+route-map costmax permit 20
+ set metric-type type-1
+ set metric +1
+ set metric-min 713
+ match ip address prefix-list min
+ exit
+!
+ip prefix-list max seq 10 permit 10.0.70.0/24
+route-map costplus permit 30
+ set metric-type type-1
+ set metric +1
+ set metric-max 13
+ match ip address prefix-list max
+ exit
+!
+route-map costplus permit 40
+ set metric-type type-1
+ set metric +1
+ exit \ No newline at end of file
diff --git a/tests/topotests/ospf_metric_propagation/r1/show_ip_route-1.json b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-1.json
new file mode 100644
index 0000000000..e3a5cc410f
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-1.json
@@ -0,0 +1,37 @@
+{
+ "10.0.94.0/24":[
+ {
+ "prefix":"10.0.94.0/24",
+ "prefixLen":24,
+ "protocol":"bgp",
+ "vrfId":9,
+ "vrfName":"green",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":34,
+ "installed":true,
+ "table":12,
+ "internalStatus":16,
+ "internalFlags":8,
+ "internalNextHopNum":1,
+ "internalNextHopActiveNum":1,
+ "nexthopGroupId":"*",
+ "installedNexthopGroupId":"*",
+ "uptime":"*",
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "ip":"10.0.10.5",
+ "afi":"ipv4",
+ "interfaceIndex":6,
+ "interfaceName":"r1-eth1",
+ "vrf":"blue",
+ "active":true,
+ "weight":1
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/ospf_metric_propagation/r1/show_ip_route-2.json b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-2.json
new file mode 100644
index 0000000000..f3597bf458
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-2.json
@@ -0,0 +1,37 @@
+{
+ "10.0.94.0/24":[
+ {
+ "prefix":"10.0.94.0/24",
+ "prefixLen":24,
+ "protocol":"bgp",
+ "vrfId":9,
+ "vrfName":"green",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":136,
+ "installed":true,
+ "table":12,
+ "internalStatus":16,
+ "internalFlags":8,
+ "internalNextHopNum":1,
+ "internalNextHopActiveNum":1,
+ "nexthopGroupId":"*",
+ "installedNexthopGroupId":"*",
+ "uptime":"*",
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "ip":"10.0.1.2",
+ "afi":"ipv4",
+ "interfaceIndex":5,
+ "interfaceName":"r1-eth0",
+ "vrf":"default",
+ "active":true,
+ "weight":1
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/ospf_metric_propagation/r1/show_ip_route-3.json b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-3.json
new file mode 100644
index 0000000000..eebcab83e4
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-3.json
@@ -0,0 +1,37 @@
+{
+ "10.0.94.0/24":[
+ {
+ "prefix":"10.0.94.0/24",
+ "prefixLen":24,
+ "protocol":"bgp",
+ "vrfId":9,
+ "vrfName":"green",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":1138,
+ "installed":true,
+ "table":12,
+ "internalStatus":16,
+ "internalFlags":8,
+ "internalNextHopNum":1,
+ "internalNextHopActiveNum":1,
+ "nexthopGroupId":"*",
+ "installedNexthopGroupId":"*",
+ "uptime":"*",
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "ip":"10.0.1.2",
+ "afi":"ipv4",
+ "interfaceIndex":5,
+ "interfaceName":"r1-eth0",
+ "vrf":"default",
+ "active":true,
+ "weight":1
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/ospf_metric_propagation/r1/show_ip_route-4.json b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-4.json
new file mode 100644
index 0000000000..d0e3d816d3
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-4.json
@@ -0,0 +1,37 @@
+{
+ "10.0.94.0/24":[
+ {
+ "prefix":"10.0.94.0/24",
+ "prefixLen":24,
+ "protocol":"bgp",
+ "vrfId":9,
+ "vrfName":"green",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":1218,
+ "installed":true,
+ "table":12,
+ "internalStatus":16,
+ "internalFlags":8,
+ "internalNextHopNum":1,
+ "internalNextHopActiveNum":1,
+ "nexthopGroupId":"*",
+ "installedNexthopGroupId":"*",
+ "uptime":"*",
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "ip":"10.0.1.2",
+ "afi":"ipv4",
+ "interfaceIndex":5,
+ "interfaceName":"r1-eth0",
+ "vrf":"default",
+ "active":true,
+ "weight":1
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/ospf_metric_propagation/r1/show_ip_route-5.json b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-5.json
new file mode 100644
index 0000000000..989ccf7798
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-5.json
@@ -0,0 +1,37 @@
+{
+ "10.0.94.0/24":[
+ {
+ "prefix":"10.0.94.0/24",
+ "prefixLen":24,
+ "protocol":"bgp",
+ "vrfId":9,
+ "vrfName":"green",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":238,
+ "installed":true,
+ "table":12,
+ "internalStatus":16,
+ "internalFlags":8,
+ "internalNextHopNum":1,
+ "internalNextHopActiveNum":1,
+ "nexthopGroupId":"*",
+ "installedNexthopGroupId":"*",
+ "uptime":"*",
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "ip":"10.0.1.2",
+ "afi":"ipv4",
+ "interfaceIndex":5,
+ "interfaceName":"r1-eth0",
+ "vrf":"default",
+ "active":true,
+ "weight":1
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/ospf_metric_propagation/r1/show_ip_route-6.json b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-6.json
new file mode 100644
index 0000000000..84b11886e4
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/r1/show_ip_route-6.json
@@ -0,0 +1,37 @@
+{
+ "10.0.94.0/24":[
+ {
+ "prefix":"10.0.94.0/24",
+ "prefixLen":24,
+ "protocol":"bgp",
+ "vrfId":9,
+ "vrfName":"green",
+ "selected":true,
+ "destSelected":true,
+ "distance":20,
+ "metric":136,
+ "installed":true,
+ "table":12,
+ "internalStatus":16,
+ "internalFlags":8,
+ "internalNextHopNum":1,
+ "internalNextHopActiveNum":1,
+ "nexthopGroupId":"*",
+ "installedNexthopGroupId":"*",
+ "uptime":"*",
+ "nexthops":[
+ {
+ "flags":3,
+ "fib":true,
+ "ip":"10.0.10.5",
+ "afi":"ipv4",
+ "interfaceIndex":6,
+ "interfaceName":"r1-eth1",
+ "vrf":"blue",
+ "active":true,
+ "weight":1
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/topotests/ospf_metric_propagation/r2/frr.conf b/tests/topotests/ospf_metric_propagation/r2/frr.conf
new file mode 100644
index 0000000000..e67a374ff5
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/r2/frr.conf
@@ -0,0 +1,81 @@
+!
+hostname r2
+password zebra
+log file /tmp/r2-frr.log
+ip forwarding
+!
+interface r2-eth0
+ ip address 10.0.1.2/24
+ ip ospf cost 100
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r2-eth1 vrf blue
+ ip address 10.0.20.2/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r2-eth2 vrf green
+ ip address 10.0.70.2/24
+ ip ospf cost 1000
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+router ospf
+ ospf router-id 10.0.255.2
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.1.0/24 area 0
+!
+router ospf vrf blue
+ ospf router-id 10.0.255.2
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.20.0/24 area 0
+!
+
+router ospf vrf green
+ ospf router-id 10.0.255.2
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.70.0/24 area 0
+!
+
+router bgp 99
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf
+ import vrf route-map rmap
+ import vrf blue
+ import vrf green
+ !
+!
+router bgp 99 vrf blue
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf
+ import vrf route-map rmap
+ import vrf default
+ import vrf green
+ !
+router bgp 99 vrf green
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf
+ import vrf route-map rmap
+ import vrf default
+ import vrf blue
+ !
+!
+route-map rmap permit 10
+ set metric-type type-1
+ set metric +1
+ exit
+!
+route-map costplus permit 1
+ set metric-type type-1
+ set metric +1
+ exit
diff --git a/tests/topotests/ospf_metric_propagation/r3/frr.conf b/tests/topotests/ospf_metric_propagation/r3/frr.conf
new file mode 100644
index 0000000000..175851d427
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/r3/frr.conf
@@ -0,0 +1,79 @@
+!
+hostname r3
+password zebra
+log file /tmp/r3-frr.log
+ip forwarding
+!
+interface r3-eth0
+ ip address 10.0.3.3/24
+ ip ospf cost 100
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r3-eth1 vrf blue
+ ip address 10.0.30.3/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r3-eth2 vrf green
+ ip address 10.0.80.3/24
+ ip ospf cost 1000
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+router ospf
+ ospf router-id 10.0.255.3
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.3.0/24 area 0
+!
+router ospf vrf blue
+ ospf router-id 10.0.255.3
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.30.0/24 area 0
+!
+router ospf vrf green
+ ospf router-id 10.0.255.3
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.80.0/24 area 0
+!
+router bgp 99
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf
+ import vrf route-map rmap
+ import vrf blue
+ import vrf green
+ !
+!
+router bgp 99 vrf blue
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf
+ import vrf route-map rmap
+ import vrf default
+ import vrf green
+ !
+router bgp 99 vrf green
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf
+ import vrf route-map rmap
+ import vrf default
+ import vrf blue
+ !
+!
+route-map rmap permit 10
+ set metric-type type-1
+ set metric +1
+ exit
+!
+route-map costplus permit 1
+ set metric-type type-1
+ set metric +1
+ exit
diff --git a/tests/topotests/ospf_metric_propagation/r4/frr.conf b/tests/topotests/ospf_metric_propagation/r4/frr.conf
new file mode 100644
index 0000000000..70a47e34fa
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/r4/frr.conf
@@ -0,0 +1,78 @@
+!
+hostname r4
+password zebra
+log file /tmp/r4-frr.log
+ip forwarding
+!
+interface r4-eth0
+ ip address 10.0.3.4/24
+ ip ospf cost 100
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r4-eth1 vrf blue
+ ip address 10.0.40.4/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface r4-eth2 vrf green
+ ip address 10.0.94.4/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+router ospf
+ ospf router-id 10.0.255.4
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.3.0/24 area 0
+!
+router ospf vrf blue
+ ospf router-id 10.0.255.4
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.40.0/24 area 0
+!
+router ospf vrf green
+ ospf router-id 10.0.255.1
+ distance 20
+ redistribute bgp route-map costplus
+ network 10.0.94.0/24 area 0
+!
+router bgp 99
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf route-map costplus
+ import vrf route-map rmap
+ import vrf blue
+ import vrf green
+ !
+!
+router bgp 99 vrf blue
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf
+ import vrf route-map rmap
+ import vrf default
+ import vrf green
+ !
+router bgp 99 vrf green
+ no bgp ebgp-requires-policy
+ address-family ipv4 unicast
+ redistribute connected
+ redistribute ospf
+ import vrf route-map rmap
+ import vrf default
+ import vrf blue
+ !
+!
+route-map rmap permit 10
+ set metric-type type-1
+ set metric +1
+ exit
+!
+route-map costplus permit 1
+ set metric-type type-1
+ set metric +1
+ exit
diff --git a/tests/topotests/ospf_metric_propagation/ra/frr.conf b/tests/topotests/ospf_metric_propagation/ra/frr.conf
new file mode 100644
index 0000000000..7be9e5c33e
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/ra/frr.conf
@@ -0,0 +1,27 @@
+!
+hostname ra
+password zebra
+log file /tmp/ra-frr.log
+ip forwarding
+!
+interface ra-eth0
+ ip address 10.0.50.5/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface ra-eth1
+ ip address 10.0.10.5/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface ra-eth2
+ ip address 10.0.20.5/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+router ospf
+ ospf router-id 10.0.255.5
+ network 10.0.10.0/24 area 0
+ network 10.0.20.0/24 area 0
+ network 10.0.50.0/24 area 0
+!
diff --git a/tests/topotests/ospf_metric_propagation/rb/frr.conf b/tests/topotests/ospf_metric_propagation/rb/frr.conf
new file mode 100644
index 0000000000..a7dbf82278
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/rb/frr.conf
@@ -0,0 +1,27 @@
+!
+hostname rb
+password zebra
+log file /tmp/rb-frr.log
+ip forwarding
+!
+interface rb-eth0
+ ip address 10.0.50.6/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface rb-eth1
+ ip address 10.0.30.6/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface rb-eth2
+ ip address 10.0.40.6/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+router ospf
+ ospf router-id 10.0.255.6
+ network 10.0.30.0/24 area 0
+ network 10.0.40.0/24 area 0
+ network 10.0.50.0/24 area 0
+!
diff --git a/tests/topotests/ospf_metric_propagation/rc/frr.conf b/tests/topotests/ospf_metric_propagation/rc/frr.conf
new file mode 100644
index 0000000000..f5a2ed7c4f
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/rc/frr.conf
@@ -0,0 +1,21 @@
+!
+hostname rc
+password zebra
+log file /tmp/rc-frr.log
+ip forwarding
+!
+interface rc-eth0
+ ip address 10.0.70.7/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+interface rc-eth1
+ ip address 10.0.80.7/24
+ ip ospf hello-interval 1
+ ip ospf dead-interval 30
+!
+router ospf
+ ospf router-id 10.0.255.7
+ network 10.0.70.0/24 area 0
+ network 10.0.80.0/24 area 0
+!
diff --git a/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py b/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py
new file mode 100644
index 0000000000..4d78bd2372
--- /dev/null
+++ b/tests/topotests/ospf_metric_propagation/test_ospf_metric_propagation.py
@@ -0,0 +1,385 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# test_ospf_metric_propagation.py
+#
+# Copyright (c) 2023 ATCorp
+# Jafar Al-Gharaibeh
+#
+
+import os
+import sys
+import json
+from time import sleep
+from functools import partial
+import pytest
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+
+"""
+test_ospf_metric_propagation.py: Test OSPF/BGP metric propagation
+"""
+
+TOPOLOGY = """
+ +-----+ +-----+
+ eth1 | | eth0 | | eth2
+ +-------------+ rA +---------------------------+ rB +---------------+
+ | .5 | | .5 .6 | | .6 |
+ | +--+--+ 10.0.50.0/24 +--+--+ .6 |
+ | |.5 |.6 |
+ | eth2| eth1| |
+ 10.0.10.0/24 | | |
+ | 10.0.20.0/24 10.0.30.0/24 10.0.40.0/24
+ |blue |blue |blue |blue
+ | | | |
+ eth1|.1 eth1|.2 eth1|.3 eth1|.4
+ +-----+ +--+--+ +--+--+ +-----+ +-+---+ +-+---+ +------+
+ | |eth0 eth2| | eth0 | |eth2 eth1| |eth2 eth3| | eth0 | |eth2 eth0| |
+ | h1 +----------+ R1 +----------+ R2 +-----------+ rC +----------+ R3 +------------+ R4 +---------+ h2 |
+ | | | | | | | | | | | | | |
+ +-----+.2 .1 +-----+.1 .2+-----+.2 .7 +-----+.7 .3+-----+.3 .4+-----+.4 .2+------+
+ green green green green
+
+ 10.0.91.0/24 10.0.1.0/24 10.0.70.0/24 10.0.80.0/24 10.0.3.0/24 10.0.94.0/24
+"""
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# Required to instantiate the topology builder class.
+
+pytestmark = [pytest.mark.ospfd, pytest.mark.bgpd]
+
+
+def build_topo(tgen):
+ "Build function"
+
+ # Create 4 routers
+ for routern in range(1, 5):
+ tgen.add_router("r{}".format(routern))
+
+ tgen.add_router("ra")
+ tgen.add_router("rb")
+ tgen.add_router("rc")
+ tgen.add_router("h1")
+ tgen.add_router("h2")
+
+ # Interconect router 1, 2
+ switch = tgen.add_switch("s1-2")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r2"])
+
+ # Interconect router 3, 4
+ switch = tgen.add_switch("s3-4")
+ switch.add_link(tgen.gears["r3"])
+ switch.add_link(tgen.gears["r4"])
+
+ # Interconect router a, b
+ switch = tgen.add_switch("sa-b")
+ switch.add_link(tgen.gears["ra"])
+ switch.add_link(tgen.gears["rb"])
+
+ # Interconect router 1, a
+ switch = tgen.add_switch("s1-a")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["ra"])
+
+ # Interconect router 2, a
+ switch = tgen.add_switch("s2-a")
+ switch.add_link(tgen.gears["r2"])
+ switch.add_link(tgen.gears["ra"])
+
+ # Interconect router 3, b
+ switch = tgen.add_switch("s3-b")
+ switch.add_link(tgen.gears["r3"])
+ switch.add_link(tgen.gears["rb"])
+
+ # Interconect router 4, b
+ switch = tgen.add_switch("s4-b")
+ switch.add_link(tgen.gears["r4"])
+ switch.add_link(tgen.gears["rb"])
+
+ # Interconect router 1, h1
+ switch = tgen.add_switch("s1-h1")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["h1"])
+
+ # Interconect router 4, h2
+ switch = tgen.add_switch("s4-h2")
+ switch.add_link(tgen.gears["r4"])
+ switch.add_link(tgen.gears["h2"])
+
+ # Interconect router 2, c
+ switch = tgen.add_switch("s2-c")
+ switch.add_link(tgen.gears["r2"])
+ switch.add_link(tgen.gears["rc"])
+
+ # Interconect router 3, c
+ switch = tgen.add_switch("s3-c")
+ switch.add_link(tgen.gears["r3"])
+ switch.add_link(tgen.gears["rc"])
+
+
+def setup_module(mod):
+ logger.info("OSPF Metric Propagation:\n {}".format(TOPOLOGY))
+
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ vrf_setup_cmds = [
+ "ip link add name blue type vrf table 11",
+ "ip link set dev blue up",
+ "ip link add name green type vrf table 12",
+ "ip link set dev green up",
+ ]
+
+ # Starting Routers
+ router_list = tgen.routers()
+
+ # Create VRFs and bind to interfaces
+ for routern in range(1, 5):
+ for cmd in vrf_setup_cmds:
+ tgen.net["r{}".format(routern)].cmd(cmd)
+ for routern in range(1, 5):
+ tgen.net["r{}".format(routern)].cmd(
+ "ip link set dev r{}-eth1 vrf blue up".format(routern)
+ )
+ tgen.net["r{}".format(routern)].cmd(
+ "ip link set dev r{}-eth2 vrf green up".format(routern)
+ )
+
+ for rname, router in router_list.items():
+ logger.info("Loading router %s" % rname)
+ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+ # Initialize all routers.
+ tgen.start_router()
+ for router in router_list.values():
+ if router.has_version("<", "4.20"):
+ tgen.set_error("unsupported version")
+
+
+def teardown_module(mod):
+ "Teardown the pytest environment"
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_all_links_up():
+ "Test path R1 -> Ra -> Rb -> R4"
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ r1 = tgen.gears["r1"]
+ json_file = "{}/r1/show_ip_route-1.json".format(CWD)
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip route vrf green 10.0.94.2 json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+
+ assertmsg = "r1 JSON output mismatches"
+ assert result is None, assertmsg
+
+
+def test_link_1_down():
+ "Test path R1 -> R2 -> Ra -> Rb -> R4"
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ tgen.net["r1"].cmd("ip link set dev r1-eth1 down")
+ r1 = tgen.gears["r1"]
+
+ json_file = "{}/r1/show_ip_route-2.json".format(CWD)
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip route vrf green 10.0.94.2 json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+
+ assertmsg = "r1 JSON output mismatches"
+ assert result is None, assertmsg
+
+
+def test_link_1_2_down():
+ "Test path R1 -> R2 -> Rc -> R3 -> Rb -> R4"
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ tgen.net["r2"].cmd("ip link set dev r2-eth1 down")
+ tgen.net["r2"].cmd("ip link set dev r2-eth2 down")
+ # ospf dead-interval is set to 30 seconds, wait 35 seconds to clear the neighbor
+ sleep(35)
+ tgen.net["r2"].cmd("ip link set dev r2-eth2 up")
+ r1 = tgen.gears["r1"]
+
+ json_file = "{}/r1/show_ip_route-3.json".format(CWD)
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip route vrf green 10.0.94.2 json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+
+ assertmsg = "r1 JSON output mismatches"
+ assert result is None, assertmsg
+
+
+def test_link_1_2_3_down():
+ "Test path R1 -> R2 -> Rc -> R3 -> R4"
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ tgen.net["r3"].cmd("ip link set dev r3-eth0 down")
+ tgen.net["r3"].cmd("ip link set dev r3-eth1 down")
+ # ospf dead-interval is set to 30 seconds, wait 35 seconds to clear the neighbor
+ sleep(35)
+ tgen.net["r3"].cmd("ip link set dev r3-eth0 up")
+ r1 = tgen.gears["r1"]
+
+ json_file = "{}/r1/show_ip_route-4.json".format(CWD)
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip route vrf green 10.0.94.2 json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+
+ assertmsg = "r1 JSON output mismatches"
+ assert result is None, assertmsg
+
+
+def test_link_1_2_3_4_down():
+ "Test path R1 -> R2 -> Rc -> R3 -> R4"
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ tgen.net["r4"].cmd("ip link set dev r4-eth1 down")
+ r1 = tgen.gears["r1"]
+
+ json_file = "{}/r1/show_ip_route-4.json".format(CWD)
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip route vrf green 10.0.94.2 json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+
+ assertmsg = "r1 JSON output mismatches"
+ assert result is None, assertmsg
+
+
+def test_link_1_2_4_down():
+ "Test path R1 -> R2 -> Rc -> R3 -> R4"
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # bring link 3 back up
+ tgen.net["r3"].cmd("ip link set dev r3-eth1 up")
+ r1 = tgen.gears["r1"]
+
+ json_file = "{}/r1/show_ip_route-4.json".format(CWD)
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip route vrf green 10.0.94.2 json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+
+ assertmsg = "r1 JSON output mismatches"
+ assert result is None, assertmsg
+
+
+def test_link_1_4_down():
+ "Test path R1 -> R2 -> Ra -> Rb -> R3 -> R4"
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # bring back link 2 up
+ tgen.net["r2"].cmd("ip link set dev r2-eth1 up")
+ r1 = tgen.gears["r1"]
+
+ json_file = "{}/r1/show_ip_route-5.json".format(CWD)
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip route vrf green 10.0.94.2 json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+
+ assertmsg = "r1 JSON output mismatches"
+ assert result is None, assertmsg
+
+
+def test_link_4_down():
+ "Test path R1 -> Ra -> Rb -> R3 -> R4"
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # bring back link 1 up
+ tgen.net["r1"].cmd("ip link set dev r1-eth1 up")
+ r1 = tgen.gears["r1"]
+
+ json_file = "{}/r1/show_ip_route-6.json".format(CWD)
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip route vrf green 10.0.94.2 json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+
+ assertmsg = "r1 JSON output mismatches"
+ assert result is None, assertmsg
+
+
+def test_link_1_2_3_4_up():
+ "Test path R1 -> Ra -> Rb -> R4"
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of router(s) failure")
+
+ # bring back link 4 up
+ tgen.net["r4"].cmd("ip link set dev r4-eth1 up")
+ r1 = tgen.gears["r1"]
+
+ json_file = "{}/r1/show_ip_route-1.json".format(CWD)
+ expected = json.loads(open(json_file).read())
+ test_func = partial(
+ topotest.router_json_cmp, r1, "show ip route vrf green 10.0.94.2 json", expected
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+
+ assertmsg = "r1 JSON output mismatches"
+ assert result 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))
diff --git a/tests/topotests/ospf_nssa_topo1/__init__.py b/tests/topotests/ospf_nssa_topo1/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/__init__.py
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/ospfd.conf b/tests/topotests/ospf_nssa_topo1/rt1/ospfd.conf
new file mode 100644
index 0000000000..6d23c84806
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/ospfd.conf
@@ -0,0 +1,22 @@
+password 1
+hostname rt1
+log file ospfd.log
+!
+! debug ospf sr
+! debug ospf te
+! debug ospf event
+! debug ospf lsa
+! debug ospf zebra
+!
+interface lo
+ ip ospf area 0
+!
+interface eth-rt2
+ ip ospf network point-to-point
+ ip ospf area 0
+ ip ospf hello-interval 3
+ ip ospf dead-interval 12
+!
+router ospf
+ router-id 1.1.1.1
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/staticd.conf b/tests/topotests/ospf_nssa_topo1/rt1/staticd.conf
new file mode 100644
index 0000000000..7ba3dc7591
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/staticd.conf
@@ -0,0 +1,6 @@
+log file staticd.log
+!
+hostname rt1
+!
+line vty
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/step1/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt1/step1/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..ac34417bda
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/step1/show_ip_ospf_route.ref
@@ -0,0 +1,115 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.0",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/step10/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt1/step10/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..ac34417bda
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/step10/show_ip_ospf_route.ref
@@ -0,0 +1,115 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.0",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/step2/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt1/step2/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..ac34417bda
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/step2/show_ip_ospf_route.ref
@@ -0,0 +1,115 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.0",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/step3/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt1/step3/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..ac34417bda
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/step3/show_ip_ospf_route.ref
@@ -0,0 +1,115 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.0",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/step4/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt1/step4/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..6a05555eec
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/step4/show_ip_ospf_route.ref
@@ -0,0 +1,103 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.0",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/step5/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt1/step5/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..6a05555eec
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/step5/show_ip_ospf_route.ref
@@ -0,0 +1,103 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.0",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/step6/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt1/step6/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..2d3c8c485f
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/step6/show_ip_ospf_route.ref
@@ -0,0 +1,91 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.0",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/step7/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt1/step7/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..6a05555eec
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/step7/show_ip_ospf_route.ref
@@ -0,0 +1,103 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.0",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/step8/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt1/step8/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..f41ee3b698
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/step8/show_ip_ospf_route.ref
@@ -0,0 +1,103 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.0",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":1000,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/step9/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt1/step9/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..2d3c8c485f
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/step9/show_ip_ospf_route.ref
@@ -0,0 +1,91 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.0",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.1.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt1/zebra.conf b/tests/topotests/ospf_nssa_topo1/rt1/zebra.conf
new file mode 100644
index 0000000000..1df100564e
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt1/zebra.conf
@@ -0,0 +1,18 @@
+log file zebra.log
+!
+hostname rt1
+!
+! debug zebra kernel
+! debug zebra packet
+! debug zebra mpls
+!
+interface lo
+ ip address 1.1.1.1/32
+!
+interface eth-rt2
+ ip address 10.0.1.1/24
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/ospfd.conf b/tests/topotests/ospf_nssa_topo1/rt2/ospfd.conf
new file mode 100644
index 0000000000..12884d2ea9
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/ospfd.conf
@@ -0,0 +1,35 @@
+password 1
+hostname rt2
+log file ospfd.log
+!
+! debug ospf sr
+! debug ospf te
+! debug ospf event
+! debug ospf lsa
+! debug ospf zebra
+!
+interface lo
+ ip ospf area 0
+!
+interface eth-rt1
+ ip ospf network point-to-point
+ ip ospf area 0
+ ip ospf hello-interval 3
+ ip ospf dead-interval 12
+!
+interface eth-rt3
+ ip ospf network point-to-point
+ ip ospf area 1
+ ip ospf hello-interval 3
+ ip ospf dead-interval 12
+!
+interface eth-rt4
+ ip ospf network point-to-point
+ ip ospf area 1
+ ip ospf hello-interval 3
+ ip ospf dead-interval 12
+!
+router ospf
+ router-id 2.2.2.2
+ area 1 nssa
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/staticd.conf b/tests/topotests/ospf_nssa_topo1/rt2/staticd.conf
new file mode 100644
index 0000000000..b6d4233a4f
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/staticd.conf
@@ -0,0 +1,6 @@
+log file staticd.log
+!
+hostname rt2
+!
+line vty
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/step1/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt2/step1/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..c09411741a
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/step1/show_ip_ospf_route.ref
@@ -0,0 +1,127 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.1",
+ "via":"eth-rt1"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt1"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt3"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt4"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/step10/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt2/step10/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..c09411741a
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/step10/show_ip_ospf_route.ref
@@ -0,0 +1,127 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.1",
+ "via":"eth-rt1"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt1"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt3"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt4"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/step2/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt2/step2/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..a67dfb4685
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/step2/show_ip_ospf_route.ref
@@ -0,0 +1,139 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.1",
+ "via":"eth-rt1"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt1"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt3"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt4"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "0.0.0.0\/0":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":1,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/step3/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt2/step3/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..c09411741a
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/step3/show_ip_ospf_route.ref
@@ -0,0 +1,127 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.1",
+ "via":"eth-rt1"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt1"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt3"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt4"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/step4/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt2/step4/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..c7dd93c6e9
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/step4/show_ip_ospf_route.ref
@@ -0,0 +1,129 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.1",
+ "via":"eth-rt1"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt1"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt3"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/step5/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt2/step5/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..9c3cfff6c0
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/step5/show_ip_ospf_route.ref
@@ -0,0 +1,117 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.1",
+ "via":"eth-rt1"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt1"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt3"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/step6/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt2/step6/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..f6bbdfa594
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/step6/show_ip_ospf_route.ref
@@ -0,0 +1,103 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.1",
+ "via":"eth-rt1"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt1"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt3"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt4"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/step7/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt2/step7/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..c7dd93c6e9
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/step7/show_ip_ospf_route.ref
@@ -0,0 +1,129 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.1",
+ "via":"eth-rt1"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt1"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt3"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/step8/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt2/step8/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..c7dd93c6e9
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/step8/show_ip_ospf_route.ref
@@ -0,0 +1,129 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.1",
+ "via":"eth-rt1"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt1"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt3"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/step9/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt2/step9/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..c09411741a
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/step9/show_ip_ospf_route.ref
@@ -0,0 +1,127 @@
+{
+ "1.1.1.1\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":"10.0.1.1",
+ "via":"eth-rt1"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.0",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt1"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt3"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt4"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.3",
+ "via":"eth-rt3"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.4",
+ "via":"eth-rt4"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt2/zebra.conf b/tests/topotests/ospf_nssa_topo1/rt2/zebra.conf
new file mode 100644
index 0000000000..fa274ebade
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt2/zebra.conf
@@ -0,0 +1,24 @@
+log file zebra.log
+!
+hostname rt2
+!
+! debug zebra kernel
+! debug zebra packet
+! debug zebra mpls
+!
+interface lo
+ ip address 2.2.2.2/32
+!
+interface eth-rt1
+ ip address 10.0.1.2/24
+!
+interface eth-rt3
+ ip address 10.0.2.2/24
+!
+interface eth-rt4
+ ip address 10.0.3.2/24
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/ospfd.conf b/tests/topotests/ospf_nssa_topo1/rt3/ospfd.conf
new file mode 100644
index 0000000000..9691a7c512
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/ospfd.conf
@@ -0,0 +1,24 @@
+password 1
+hostname rt3
+log file ospfd.log
+!
+! debug ospf sr
+! debug ospf te
+! debug ospf event
+! debug ospf lsa
+! debug ospf zebra
+!
+interface lo
+ ip ospf area 1
+!
+interface eth-rt2
+ ip ospf network point-to-point
+ ip ospf area 1
+ ip ospf hello-interval 3
+ ip ospf dead-interval 12
+!
+router ospf
+ router-id 3.3.3.3
+ area 1 nssa
+ redistribute connected
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/staticd.conf b/tests/topotests/ospf_nssa_topo1/rt3/staticd.conf
new file mode 100644
index 0000000000..f0edd6c9ca
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/staticd.conf
@@ -0,0 +1,8 @@
+log file staticd.log
+!
+hostname rt3
+!
+ip route 0.0.0.0/0 Null0
+!
+line vty
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/step1/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt3/step1/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..a2d078ab13
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/step1/show_ip_ospf_route.ref
@@ -0,0 +1,138 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/step10/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt3/step10/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..4619067abd
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/step10/show_ip_ospf_route.ref
@@ -0,0 +1,150 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":1000,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/step2/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt3/step2/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..a2d078ab13
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/step2/show_ip_ospf_route.ref
@@ -0,0 +1,138 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/step3/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt3/step3/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..a2d078ab13
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/step3/show_ip_ospf_route.ref
@@ -0,0 +1,138 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/step4/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt3/step4/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..10387215b2
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/step4/show_ip_ospf_route.ref
@@ -0,0 +1,150 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/step5/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt3/step5/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..4f8eaf1eaa
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/step5/show_ip_ospf_route.ref
@@ -0,0 +1,138 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/step6/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt3/step6/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..41e9f6718a
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/step6/show_ip_ospf_route.ref
@@ -0,0 +1,126 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/step7/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt3/step7/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..10387215b2
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/step7/show_ip_ospf_route.ref
@@ -0,0 +1,150 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/step8/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt3/step8/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..4619067abd
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/step8/show_ip_ospf_route.ref
@@ -0,0 +1,150 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":1000,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/step9/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt3/step9/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..4619067abd
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/step9/show_ip_ospf_route.ref
@@ -0,0 +1,150 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":1000,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.1\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.2\/32":{
+ "routeType":"N E2",
+ "cost":20,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.2.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt3/zebra.conf b/tests/topotests/ospf_nssa_topo1/rt3/zebra.conf
new file mode 100644
index 0000000000..d943540f3b
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt3/zebra.conf
@@ -0,0 +1,18 @@
+log file zebra.log
+!
+hostname rt3
+!
+! debug zebra kernel
+! debug zebra packet
+! debug zebra mpls
+!
+interface lo
+ ip address 3.3.3.3/32
+!
+interface eth-rt2
+ ip address 10.0.2.3/24
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/ospfd.conf b/tests/topotests/ospf_nssa_topo1/rt4/ospfd.conf
new file mode 100644
index 0000000000..cba7cf71ed
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/ospfd.conf
@@ -0,0 +1,24 @@
+password 1
+hostname rt4
+log file ospfd.log
+!
+! debug ospf sr
+! debug ospf te
+! debug ospf event
+! debug ospf lsa
+! debug ospf zebra
+!
+interface lo
+ ip ospf area 1
+!
+interface eth-rt2
+ ip ospf network point-to-point
+ ip ospf area 1
+ ip ospf hello-interval 3
+ ip ospf dead-interval 12
+!
+router ospf
+ router-id 4.4.4.4
+ area 1 nssa
+ redistribute static
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/staticd.conf b/tests/topotests/ospf_nssa_topo1/rt4/staticd.conf
new file mode 100644
index 0000000000..e00ee5dfea
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/staticd.conf
@@ -0,0 +1,9 @@
+log file staticd.log
+!
+hostname rt4
+!
+ip route 172.16.1.1/32 Null0
+ip route 172.16.1.2/32 Null0
+!
+line vty
+!
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/step1/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt4/step1/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..e57f542f1c
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/step1/show_ip_ospf_route.ref
@@ -0,0 +1,114 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/step10/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt4/step10/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..82a0e1a9b4
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/step10/show_ip_ospf_route.ref
@@ -0,0 +1,126 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":1000,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/step2/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt4/step2/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..e57f542f1c
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/step2/show_ip_ospf_route.ref
@@ -0,0 +1,114 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/step3/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt4/step3/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..e57f542f1c
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/step3/show_ip_ospf_route.ref
@@ -0,0 +1,114 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/step4/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt4/step4/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..5f51b3b3b8
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/step4/show_ip_ospf_route.ref
@@ -0,0 +1,126 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/step5/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt4/step5/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..5f51b3b3b8
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/step5/show_ip_ospf_route.ref
@@ -0,0 +1,126 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/step6/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt4/step6/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..5f51b3b3b8
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/step6/show_ip_ospf_route.ref
@@ -0,0 +1,126 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/step7/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt4/step7/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..5f51b3b3b8
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/step7/show_ip_ospf_route.ref
@@ -0,0 +1,126 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":20,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/step8/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt4/step8/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..82a0e1a9b4
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/step8/show_ip_ospf_route.ref
@@ -0,0 +1,126 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":1000,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/step9/show_ip_ospf_route.ref b/tests/topotests/ospf_nssa_topo1/rt4/step9/show_ip_ospf_route.ref
new file mode 100644
index 0000000000..82a0e1a9b4
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/step9/show_ip_ospf_route.ref
@@ -0,0 +1,126 @@
+{
+ "0.0.0.0\/0":{
+ "routeType":"N IA",
+ "cost":11,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "1.1.1.1\/32":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2\/32":{
+ "routeType":"N IA",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3\/32":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "4.4.4.4\/32":{
+ "routeType":"N",
+ "cost":0,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"lo"
+ }
+ ]
+ },
+ "10.0.1.0\/24":{
+ "routeType":"N IA",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.2.0\/24":{
+ "routeType":"N",
+ "cost":20,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "10.0.3.0\/24":{
+ "routeType":"N",
+ "cost":10,
+ "area":"0.0.0.1",
+ "nexthops":[
+ {
+ "ip":" ",
+ "directlyAttachedTo":"eth-rt2"
+ }
+ ]
+ },
+ "2.2.2.2":{
+ "routeType":"R ",
+ "cost":10,
+ "area":"0.0.0.1",
+ "routerType":"abr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "3.3.3.3":{
+ "routeType":"R ",
+ "cost":20,
+ "area":"0.0.0.1",
+ "routerType":"asbr",
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ },
+ "172.16.1.0\/24":{
+ "routeType":"N E2",
+ "cost":10,
+ "type2cost":1000,
+ "tag":0,
+ "nexthops":[
+ {
+ "ip":"10.0.3.2",
+ "via":"eth-rt2"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/ospf_nssa_topo1/rt4/zebra.conf b/tests/topotests/ospf_nssa_topo1/rt4/zebra.conf
new file mode 100644
index 0000000000..588febe70b
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/rt4/zebra.conf
@@ -0,0 +1,18 @@
+log file zebra.log
+!
+hostname rt4
+!
+! debug zebra kernel
+! debug zebra packet
+! debug zebra mpls
+!
+interface lo
+ ip address 4.4.4.4/32
+!
+interface eth-rt2
+ ip address 10.0.3.4/24
+!
+ip forwarding
+!
+line vty
+!
diff --git a/tests/topotests/ospf_nssa_topo1/test_ospf_nssa_topo1.py b/tests/topotests/ospf_nssa_topo1/test_ospf_nssa_topo1.py
new file mode 100644
index 0000000000..432ddf0986
--- /dev/null
+++ b/tests/topotests/ospf_nssa_topo1/test_ospf_nssa_topo1.py
@@ -0,0 +1,416 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# test_ospf_nssa_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2023 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+
+"""
+test_ospf_nssa_topo1.py:
+
+ +---------+
+ | RT1 |
+ | 1.1.1.1 |
+ +---------+
+ |eth-rt2
+ |
+ |10.0.1.0/24
+ |
+ |eth-rt1
+ +---------+
+ | RT2 |
+ | 2.2.2.2 |
+ +---------+
+ eth-rt3| |eth-rt4
+ | |
+ 10.0.2.0/24 | | 10.0.3.0/24
+ +---------+ +--------+
+ | |
+ |eth-rt2 |eth-rt2
+ +---------+ +---------+
+ | RT3 | | RT4 |
+ | 3.3.3.3 | | 4.4.4.4 |
+ +---------+ +---------+
+
+"""
+
+import os
+import sys
+import pytest
+import json
+from functools import partial
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+# Required to instantiate the topology builder class.
+
+pytestmark = [pytest.mark.ospfd]
+
+
+def build_topo(tgen):
+ "Build function"
+
+ #
+ # Define FRR Routers
+ #
+ for router in ["rt1", "rt2", "rt3", "rt4"]:
+ tgen.add_router(router)
+
+ #
+ # Define connections
+ #
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["rt1"], nodeif="eth-rt2")
+ switch.add_link(tgen.gears["rt2"], nodeif="eth-rt1")
+
+ switch = tgen.add_switch("s2")
+ switch.add_link(tgen.gears["rt2"], nodeif="eth-rt3")
+ switch.add_link(tgen.gears["rt3"], nodeif="eth-rt2")
+
+ switch = tgen.add_switch("s3")
+ switch.add_link(tgen.gears["rt2"], nodeif="eth-rt4")
+ switch.add_link(tgen.gears["rt4"], nodeif="eth-rt2")
+
+
+def setup_module(mod):
+ "Sets up the pytest environment"
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+
+ # For all registered routers, load the zebra configuration file
+ for rname, router in router_list.items():
+ router.load_config(
+ TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname))
+ )
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ "Teardown the pytest environment"
+ tgen = get_topogen()
+
+ # This function tears down the whole topology.
+ tgen.stop_topology()
+
+
+def print_cmd_result(rname, command):
+ print(get_topogen().gears[rname].vtysh_cmd(command, isjson=False))
+
+
+def router_compare_json_output(rname, command, reference):
+ "Compare router JSON output"
+
+ logger.info('Comparing router "%s" "%s" output', rname, command)
+
+ tgen = get_topogen()
+ filename = "{}/{}/{}".format(CWD, rname, reference)
+ expected = json.loads(open(filename).read())
+
+ # Run test function until we get an result. Wait at most 60 seconds.
+ test_func = partial(topotest.router_json_cmp, tgen.gears[rname], command, expected)
+ _, diff = topotest.run_and_expect(test_func, None, count=120, wait=0.5)
+ assertmsg = '"{}" JSON output mismatches the expected result'.format(rname)
+ assert diff is None, assertmsg
+
+
+#
+# Step 1
+#
+# Test initial network convergence
+#
+def test_rib_step1():
+ logger.info("Test (step 1): test initial network convergence")
+ tgen = get_topogen()
+
+ # Skip if previous fatal error condition is raised
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ for rname in ["rt1", "rt2", "rt3", "rt4"]:
+ router_compare_json_output(
+ rname, "show ip ospf route json", "step1/show_ip_ospf_route.ref"
+ )
+
+
+#
+# Step 2
+#
+# Action(s):
+# -rt3: configure an NSSA default route
+#
+# Expected changes:
+# -rt2: add NSSA default route pointing to rt3
+#
+def test_rib_step2():
+ logger.info("Test (step 2): verify OSPF routes")
+ tgen = get_topogen()
+
+ # Skip if previous fatal error condition is raised
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("Adding NSSA default on rt4")
+ tgen.net["rt3"].cmd(
+ 'vtysh -c "conf t" -c "router ospf" -c "area 1 nssa default-information-originate"'
+ )
+
+ for rname in ["rt1", "rt2", "rt3", "rt4"]:
+ router_compare_json_output(
+ rname, "show ip ospf route json", "step2/show_ip_ospf_route.ref"
+ )
+
+
+#
+# Step 3
+#
+# Action(s):
+# -rt3: remove NSSA default route
+#
+# Expected changes:
+# -rt2: remove NSSA default route
+#
+def test_rib_step3():
+ logger.info("Test (step 3): verify OSPF routes")
+ tgen = get_topogen()
+
+ # Skip if previous fatal error condition is raised
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("Removing NSSA default on rt4")
+ tgen.net["rt3"].cmd(
+ 'vtysh -c "conf t" -c "router ospf" -c "area 1 nssa"'
+ )
+
+ for rname in ["rt1", "rt2", "rt3", "rt4"]:
+ router_compare_json_output(
+ rname, "show ip ospf route json", "step3/show_ip_ospf_route.ref"
+ )
+
+
+#
+# Step 4
+#
+# Action(s):
+# -rt2: configure an NSSA range for 172.16.1.0/24
+#
+# Expected changes:
+# -rt1: the 172.16.1.1/32 and 172.16.1.2/32 routes should be removed
+# -rt1: the 172.16.1.0/24 route should be added
+#
+def test_rib_step4():
+ logger.info("Test (step 4): verify OSPF routes")
+ tgen = get_topogen()
+
+ # Skip if previous fatal error condition is raised
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("Configuring NSSA range on rt2")
+ tgen.net["rt2"].cmd(
+ 'vtysh -c "conf t" -c "router ospf" -c "area 1 nssa range 172.16.1.0/24"'
+ )
+
+ for rname in ["rt1", "rt2", "rt3", "rt4"]:
+ router_compare_json_output(
+ rname, "show ip ospf route json", "step4/show_ip_ospf_route.ref"
+ )
+
+
+#
+# Step 5
+#
+# Action(s):
+# -rt4: remove the 172.16.1.1/32 static route
+#
+# Expected changes:
+# -None (the 172.16.1.0/24 range is still active because of 172.16.1.2/32)
+#
+def test_rib_step5():
+ logger.info("Test (step 5): verify OSPF routes")
+ tgen = get_topogen()
+
+ # Skip if previous fatal error condition is raised
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("Removing first static route in rt4")
+ tgen.net["rt4"].cmd('vtysh -c "conf t" -c "no ip route 172.16.1.1/32 Null0"')
+
+ for rname in ["rt1", "rt2", "rt3", "rt4"]:
+ router_compare_json_output(
+ rname, "show ip ospf route json", "step5/show_ip_ospf_route.ref"
+ )
+
+
+#
+# Step 6
+#
+# Action(s):
+# -rt4: remove the 172.16.1.2/32 static route
+#
+# Expected changes:
+# -rt1: remove the 172.16.1.0/24 route since the NSSA range is no longer active
+#
+def test_rib_step6():
+ logger.info("Test (step 6): verify OSPF routes")
+ tgen = get_topogen()
+
+ # Skip if previous fatal error condition is raised
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("Removing second static route in rt4")
+ tgen.net["rt4"].cmd('vtysh -c "conf t" -c "no ip route 172.16.1.2/32 Null0"')
+
+ for rname in ["rt1", "rt2", "rt3", "rt4"]:
+ router_compare_json_output(
+ rname, "show ip ospf route json", "step6/show_ip_ospf_route.ref"
+ )
+
+
+#
+# Step 7
+#
+# Action(s):
+# -rt4: readd the 172.16.1.1/32 and 172.16.1.2/32 static routes
+#
+# Expected changes:
+# -rt1: readd the 172.16.1.0/24 route since the NSSA range is active again
+#
+def test_rib_step7():
+ logger.info("Test (step 7): verify OSPF routes")
+ tgen = get_topogen()
+
+ # Skip if previous fatal error condition is raised
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("Readding static routes in rt4")
+ tgen.net["rt4"].cmd('vtysh -c "conf t" -c "ip route 172.16.1.1/32 Null0"')
+ tgen.net["rt4"].cmd('vtysh -c "conf t" -c "ip route 172.16.1.2/32 Null0"')
+
+ for rname in ["rt1", "rt2", "rt3", "rt4"]:
+ router_compare_json_output(
+ rname, "show ip ospf route json", "step7/show_ip_ospf_route.ref"
+ )
+
+
+#
+# Step 8
+#
+# Action(s):
+# -rt2: update the NSSA range with a static cost
+#
+# Expected changes:
+# -rt1: update the metric of the 172.16.1.0/24 route from 20 to 1000
+#
+def test_rib_step8():
+ logger.info("Test (step 8): verify OSPF routes")
+ tgen = get_topogen()
+
+ # Skip if previous fatal error condition is raised
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("Updating the NSSA range cost on rt2")
+ tgen.net["rt2"].cmd(
+ 'vtysh -c "conf t" -c "router ospf" -c "area 1 nssa range 172.16.1.0/24 cost 1000"'
+ )
+
+ for rname in ["rt1", "rt2", "rt3", "rt4"]:
+ router_compare_json_output(
+ rname, "show ip ospf route json", "step8/show_ip_ospf_route.ref"
+ )
+
+
+#
+# Step 9
+#
+# Action(s):
+# -rt2: update the NSSA range to not advertise itself
+#
+# Expected changes:
+# -rt1: the 172.16.1.0/24 route should be removed
+#
+def test_rib_step9():
+ logger.info("Test (step 9): verify OSPF routes")
+ tgen = get_topogen()
+
+ # Skip if previous fatal error condition is raised
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("Updating the NSSA range to not advertise itself")
+ tgen.net["rt2"].cmd(
+ 'vtysh -c "conf t" -c "router ospf" -c "area 1 nssa range 172.16.1.0/24 not-advertise"'
+ )
+
+ for rname in ["rt1", "rt2", "rt3", "rt4"]:
+ router_compare_json_output(
+ rname, "show ip ospf route json", "step9/show_ip_ospf_route.ref"
+ )
+
+
+#
+# Step 10
+#
+# Action(s):
+# -rt2: remove the NSSA range
+#
+# Expected changes:
+# -rt1: the 172.16.1.1/32 and 172.16.1.2/32 routes should be added
+#
+def test_rib_step10():
+ logger.info("Test (step 10): verify OSPF routes")
+ tgen = get_topogen()
+
+ # Skip if previous fatal error condition is raised
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ logger.info("Removing NSSA range on rt2")
+ tgen.net["rt2"].cmd(
+ 'vtysh -c "conf t" -c "router ospf" -c "no area 1 nssa range 172.16.1.0/24"'
+ )
+
+ for rname in ["rt1", "rt2", "rt3", "rt4"]:
+ router_compare_json_output(
+ rname, "show ip ospf route json", "step10/show_ip_ospf_route.ref"
+ )
+
+
+# Memory leak test template
+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))
diff --git a/tests/topotests/ospf_suppress_fa/test_ospf_suppress_fa.py b/tests/topotests/ospf_suppress_fa/test_ospf_suppress_fa.py
index 5b1a53b895..d5583ac06a 100644
--- a/tests/topotests/ospf_suppress_fa/test_ospf_suppress_fa.py
+++ b/tests/topotests/ospf_suppress_fa/test_ospf_suppress_fa.py
@@ -111,9 +111,7 @@ def ospf_unconfigure_suppress_fa(router_name, area):
tgen = get_topogen()
router = tgen.gears[router_name]
- router.vtysh_cmd(
- "conf t\nrouter ospf\nno area {} nssa suppress-fa\nexit\n".format(area)
- )
+ router.vtysh_cmd("conf t\nrouter ospf\narea {} nssa\nexit\n".format(area))
def ospf_get_lsa_type5(router_name):
diff --git a/tests/topotests/ospf_te_topo1/reference/ted_step1.json b/tests/topotests/ospf_te_topo1/reference/ted_step1.json
index 18aee4ffab..8b2413a894 100644
--- a/tests/topotests/ospf_te_topo1/reference/ted_step1.json
+++ b/tests/topotests/ospf_te_topo1/reference/ted_step1.json
@@ -57,7 +57,7 @@
],
"edges":[
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -101,7 +101,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -142,7 +142,7 @@
}
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -183,7 +183,7 @@
}
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -224,7 +224,7 @@
}
},
{
- "edge-id":167772929,
+ "edge-id":"10.0.3.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.3",
@@ -266,7 +266,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -307,7 +307,7 @@
}
},
{
- "edge-id":167773185,
+ "edge-id":"10.0.4.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.4",
@@ -360,7 +360,7 @@
]
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -404,7 +404,7 @@
}
},
{
- "edge-id":167773441,
+ "edge-id":"10.0.5.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.3",
diff --git a/tests/topotests/ospf_te_topo1/reference/ted_step2.json b/tests/topotests/ospf_te_topo1/reference/ted_step2.json
index 1ed7272560..625b57d15c 100644
--- a/tests/topotests/ospf_te_topo1/reference/ted_step2.json
+++ b/tests/topotests/ospf_te_topo1/reference/ted_step2.json
@@ -57,7 +57,7 @@
],
"edges":[
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -101,7 +101,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -142,7 +142,7 @@
}
},
{
- "edge-id":167772929,
+ "edge-id":"10.0.3.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.3",
@@ -184,7 +184,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -225,7 +225,7 @@
}
},
{
- "edge-id":167773185,
+ "edge-id":"10.0.4.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.4",
@@ -278,7 +278,7 @@
]
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -322,7 +322,7 @@
}
},
{
- "edge-id":167773441,
+ "edge-id":"10.0.5.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.3",
diff --git a/tests/topotests/ospf_te_topo1/reference/ted_step3.json b/tests/topotests/ospf_te_topo1/reference/ted_step3.json
index 0e79670c78..4cfec0f608 100644
--- a/tests/topotests/ospf_te_topo1/reference/ted_step3.json
+++ b/tests/topotests/ospf_te_topo1/reference/ted_step3.json
@@ -49,7 +49,7 @@
],
"edges":[
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -93,7 +93,7 @@
}
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -134,7 +134,7 @@
}
},
{
- "edge-id":167772929,
+ "edge-id":"10.0.3.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.3",
@@ -176,7 +176,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -217,7 +217,7 @@
}
},
{
- "edge-id":167773185,
+ "edge-id":"10.0.4.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.4",
@@ -270,7 +270,7 @@
]
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
diff --git a/tests/topotests/ospf_te_topo1/reference/ted_step4.json b/tests/topotests/ospf_te_topo1/reference/ted_step4.json
index 860dcb3f21..e8e24d9805 100644
--- a/tests/topotests/ospf_te_topo1/reference/ted_step4.json
+++ b/tests/topotests/ospf_te_topo1/reference/ted_step4.json
@@ -72,7 +72,7 @@
],
"edges":[
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -128,7 +128,7 @@
]
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -181,7 +181,7 @@
]
},
{
- "edge-id":167772929,
+ "edge-id":"10.0.3.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.3",
@@ -223,7 +223,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -276,7 +276,7 @@
]
},
{
- "edge-id":167773185,
+ "edge-id":"10.0.4.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.4",
@@ -329,7 +329,7 @@
]
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
diff --git a/tests/topotests/ospf_te_topo1/reference/ted_step5.json b/tests/topotests/ospf_te_topo1/reference/ted_step5.json
index 615a691c45..4713cc0115 100644
--- a/tests/topotests/ospf_te_topo1/reference/ted_step5.json
+++ b/tests/topotests/ospf_te_topo1/reference/ted_step5.json
@@ -72,7 +72,7 @@
],
"edges":[
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -128,7 +128,7 @@
]
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -181,7 +181,7 @@
]
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -234,7 +234,7 @@
]
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -287,7 +287,7 @@
]
},
{
- "edge-id":167772929,
+ "edge-id":"10.0.3.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.3",
@@ -329,7 +329,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -382,7 +382,7 @@
]
},
{
- "edge-id":167773185,
+ "edge-id":"10.0.4.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.4",
@@ -435,7 +435,7 @@
]
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
diff --git a/tests/topotests/ospf_te_topo1/reference/ted_step6.json b/tests/topotests/ospf_te_topo1/reference/ted_step6.json
index 3b84d25808..aaac07b051 100644
--- a/tests/topotests/ospf_te_topo1/reference/ted_step6.json
+++ b/tests/topotests/ospf_te_topo1/reference/ted_step6.json
@@ -72,7 +72,7 @@
],
"edges":[
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -128,7 +128,7 @@
]
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -181,7 +181,7 @@
]
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -234,7 +234,7 @@
]
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -287,7 +287,7 @@
]
},
{
- "edge-id":167772929,
+ "edge-id":"10.0.3.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.3",
@@ -329,7 +329,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -382,7 +382,7 @@
]
},
{
- "edge-id":167773185,
+ "edge-id":"10.0.4.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.4",
@@ -437,7 +437,7 @@
]
},
{
- "edge-id":167773186,
+ "edge-id":"10.0.4.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
diff --git a/tests/topotests/ospf_te_topo1/reference/ted_step7.json b/tests/topotests/ospf_te_topo1/reference/ted_step7.json
index 83f8a1d5d6..56ed1f176b 100644
--- a/tests/topotests/ospf_te_topo1/reference/ted_step7.json
+++ b/tests/topotests/ospf_te_topo1/reference/ted_step7.json
@@ -53,7 +53,7 @@
],
"edges":[
{
- "edge-id":167772161,
+ "edge-id":"10.0.0.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -109,7 +109,7 @@
]
},
{
- "edge-id":167772162,
+ "edge-id":"10.0.0.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -162,7 +162,7 @@
]
},
{
- "edge-id":167772417,
+ "edge-id":"10.0.1.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.1",
@@ -215,7 +215,7 @@
]
},
{
- "edge-id":167772418,
+ "edge-id":"10.0.1.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
@@ -268,7 +268,7 @@
]
},
{
- "edge-id":167772929,
+ "edge-id":"10.0.3.1",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.3",
@@ -310,7 +310,7 @@
}
},
{
- "edge-id":167772930,
+ "edge-id":"10.0.3.2",
"status":"Sync",
"origin":"OSPFv2",
"advertised-router":"10.0.255.2",
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py
index 55166367c1..b0e56e619a 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py
@@ -156,7 +156,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf_covergence
)
@@ -280,7 +280,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step(
"Configure External Route summary in R0 to summarise 5"
@@ -314,7 +314,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -330,9 +330,9 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv6"], "next_hop": "blackhole"}]}
}
@@ -340,7 +340,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -349,7 +349,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
step("Delete the configured summary")
ospf_summ_r1 = {
@@ -374,7 +374,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -383,9 +383,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary Route still present in RIB".format(
- tc_name
- )
+ ), "Testcase {} : Failed Error: Summary Route still present in RIB".format(tc_name)
step("show ip ospf summary should not have any summary address.")
input_dict = {
@@ -403,7 +401,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary still present in DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary still present in DB".format(tc_name)
dut = "r1"
step("All 5 routes are advertised after deletion of configured summary.")
@@ -414,7 +412,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("configure the summary again and delete static routes .")
ospf_summ_r1 = {
@@ -442,7 +440,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
input_dict = {
"r0": {
@@ -461,7 +459,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict_summary, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -470,7 +468,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
step("Add back static routes.")
input_dict_static_rtes = {
@@ -488,7 +486,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict_static_rtes, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -497,7 +495,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
input_dict_summary = {"r0": {"static_routes": [{"network": SUMMARY["ipv6"][0]}]}}
dut = "r1"
@@ -508,7 +506,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show configure summaries.")
@@ -525,7 +523,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Configure new static route which is matching configured summary.")
input_dict_static_rtes = {
@@ -591,7 +589,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Shut one of the interface")
intf = topo["routers"]["r0"]["links"]["r3-link0"]["interface"]
@@ -663,7 +661,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
input_dict_summary = {"r0": {"static_routes": [{"network": SUMMARY["ipv6"][0]}]}}
@@ -674,7 +672,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
ospf_summ_r1 = {
"r0": {
@@ -702,7 +700,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict_summary, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -711,7 +709,7 @@ def test_ospfv3_type5_summary_tc42_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
ospf_summ_r1 = {
"r0": {
@@ -774,11 +772,9 @@ def test_ospfv3_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
"ospf6": {
@@ -804,7 +800,7 @@ def test_ospfv3_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -820,7 +816,7 @@ def test_ospfv3_type5_summary_tc43_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Change the summary address mask to lower match (ex - 16 to 8)")
ospf_summ_r1 = {
@@ -855,7 +851,7 @@ def test_ospfv3_type5_summary_tc43_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step(
"Verify that external routes(static / connected) are summarised"
@@ -871,7 +867,7 @@ def test_ospfv3_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Change the summary address mask to higher match (ex - 8 to 24)")
ospf_summ_r1 = {
@@ -899,7 +895,7 @@ def test_ospfv3_type5_summary_tc43_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step(
"Verify that external routes(static / connected) are summarised"
@@ -920,7 +916,7 @@ def test_ospfv3_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step(" Un configure one of the summary address.")
ospf_summ_r1 = {
@@ -955,7 +951,7 @@ def test_ospfv3_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
ospf_summ_r1 = {
"r0": {
@@ -982,7 +978,7 @@ def test_ospfv3_type5_summary_tc43_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
write_test_footer(tc_name)
@@ -1030,11 +1026,9 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
"ospf6": {
@@ -1066,7 +1060,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries with tag.")
input_dict = {
@@ -1082,7 +1076,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Delete the configured summary")
ospf_summ_r1 = {
@@ -1107,7 +1101,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -1116,9 +1110,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary Route still present in RIB".format(
- tc_name
- )
+ ), "Testcase {} : Failed Error: Summary Route still present in RIB".format(tc_name)
step("show ip ospf summary should not have any summary address.")
input_dict = {
@@ -1136,7 +1128,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary still present in DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary still present in DB".format(tc_name)
step("Configure Min tag value")
ospf_summ_r1 = {
@@ -1161,7 +1153,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries with tag.")
input_dict = {
@@ -1177,7 +1169,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Configure Max Tag Value")
ospf_summ_r1 = {
@@ -1207,7 +1199,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step(
"Verify that boundary values tags are used for summary route"
@@ -1226,7 +1218,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("configure new static route with different tag.")
input_dict_static_rtes_11 = {
@@ -1251,7 +1243,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -1266,7 +1258,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
step(
"Verify that boundary values tags are used for summary route"
@@ -1287,7 +1279,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Delete the configured summary address")
ospf_summ_r1 = {
@@ -1318,7 +1310,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that summary address is flushed from neighbor.")
@@ -1326,7 +1318,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict_summary, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -1335,7 +1327,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
step("Configure summary first & then configure matching static route.")
@@ -1467,11 +1459,9 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
"ospf6": {
@@ -1503,7 +1493,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries with tag.")
input_dict = {
@@ -1541,7 +1531,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -1550,9 +1540,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary Route still present in RIB".format(
- tc_name
- )
+ ), "Testcase {} : Failed Error: Summary Route still present in RIB".format(tc_name)
step("show ip ospf summary should not have any summary address.")
input_dict = {
@@ -1570,7 +1558,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary still present in DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary still present in DB".format(tc_name)
step("Configure Min tag value")
ospf_summ_r1 = {
@@ -1595,7 +1583,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries with tag.")
input_dict = {
@@ -1611,7 +1599,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Configure Max Tag Value")
ospf_summ_r1 = {
@@ -1641,7 +1629,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step(
"Verify that boundary values tags are used for summary route"
@@ -1660,7 +1648,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("configure new static route with different tag.")
input_dict_static_rtes_11 = {
@@ -1685,7 +1673,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -1700,7 +1688,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
step(
"Verify that boundary values tags are used for summary route"
@@ -1721,7 +1709,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Delete the configured summary address")
ospf_summ_r1 = {
@@ -1752,7 +1740,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that summary address is flushed from neighbor.")
@@ -1760,7 +1748,7 @@ def ospfv3_type5_summary_tc45_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict_summary, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -1769,7 +1757,7 @@ def ospfv3_type5_summary_tc45_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
step("Configure summary first & then configure matching static route.")
@@ -1853,7 +1841,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step(
"Configure External Route summary in R0 to summarise 5"
@@ -1887,7 +1875,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict_summary, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -1896,9 +1884,9 @@ def test_ospfv3_type5_summary_tc46_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
- step("Verify that show ip ospf summary should show the " "configured summaries.")
+ step("Verify that show ip ospf summary should show the configured summaries.")
input_dict = {
SUMMARY["ipv6"][0]: {
"summaryAddress": SUMMARY["ipv6"][0],
@@ -1909,7 +1897,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Delete the configured summary")
ospf_summ_r1 = {
@@ -1936,7 +1924,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -1945,9 +1933,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary Route still present in RIB".format(
- tc_name
- )
+ ), "Testcase {} : Failed Error: Summary Route still present in RIB".format(tc_name)
step("show ip ospf summary should not have any summary address.")
input_dict = {
@@ -1965,7 +1951,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Summary still present in DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary still present in DB".format(tc_name)
step("Reconfigure summary with no advertise.")
ospf_summ_r1 = {
@@ -1996,7 +1982,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict_summary, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -2005,9 +1991,9 @@ def test_ospfv3_type5_summary_tc46_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
- step("Verify that show ip ospf summary should show the " "configured summaries.")
+ step("Verify that show ip ospf summary should show the configured summaries.")
input_dict = {
SUMMARY["ipv6"][0]: {
"summaryAddress": SUMMARY["ipv6"][0],
@@ -2018,7 +2004,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step(
"Change summary address from no advertise to advertise "
@@ -2067,7 +2053,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -2083,9 +2069,9 @@ def test_ospfv3_type5_summary_tc46_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
output = tgen.gears["r0"].vtysh_cmd(
"show ipv6 ospf6 database as-external json", isjson=True
)
@@ -2101,7 +2087,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -2110,7 +2096,7 @@ def test_ospfv3_type5_summary_tc46_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes is present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is present in RIB".format(tc_name)
write_test_footer(tc_name)
@@ -2157,11 +2143,9 @@ def test_ospfv3_type5_summary_tc48_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
@@ -2189,7 +2173,7 @@ def test_ospfv3_type5_summary_tc48_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -2205,9 +2189,9 @@ def test_ospfv3_type5_summary_tc48_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv6"], "next_hop": "blackhole"}]}
}
@@ -2215,7 +2199,7 @@ def test_ospfv3_type5_summary_tc48_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -2224,7 +2208,7 @@ def test_ospfv3_type5_summary_tc48_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
step(
"Configure route map and & rule to permit configured summary address,"
@@ -2287,7 +2271,7 @@ def test_ospfv3_type5_summary_tc48_p0(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
input_dict = {
SUMMARY["ipv6"][0]: {
@@ -2302,7 +2286,7 @@ def test_ospfv3_type5_summary_tc48_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Configure metric type as 1 in route map.")
@@ -2339,7 +2323,7 @@ def test_ospfv3_type5_summary_tc48_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Un configure metric type from route map.")
@@ -2376,7 +2360,7 @@ def test_ospfv3_type5_summary_tc48_p0(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
step("Change rule from permit to deny in prefix list.")
pfx_list = {
@@ -2407,7 +2391,7 @@ def test_ospfv3_type5_summary_tc48_p0(request):
result = verify_ospf6_rib(tgen, dut, input_dict_summary, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -2416,7 +2400,7 @@ def test_ospfv3_type5_summary_tc48_p0(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
write_test_footer(tc_name)
@@ -2556,7 +2540,7 @@ def test_ospfv3_type5_summary_tc51_p2(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
write_test_footer(tc_name)
@@ -2603,11 +2587,9 @@ def test_ospfv3_type5_summary_tc49_p2(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_static_rtes, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
- step(
- "Configure External Route summary in R0 to summarise 5" " routes to one route."
- )
+ step("Configure External Route summary in R0 to summarise 5 routes to one route.")
ospf_summ_r1 = {
"r0": {
@@ -2635,7 +2617,7 @@ def test_ospfv3_type5_summary_tc49_p2(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -2651,9 +2633,9 @@ def test_ospfv3_type5_summary_tc49_p2(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv6"], "next_hop": "blackhole"}]}
}
@@ -2661,7 +2643,7 @@ def test_ospfv3_type5_summary_tc49_p2(request):
result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -2670,7 +2652,7 @@ def test_ospfv3_type5_summary_tc49_p2(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
step("Reload the FRR router")
# stop/start -> restart FRR router and verify
@@ -2691,7 +2673,7 @@ def test_ospfv3_type5_summary_tc49_p2(request):
result = verify_rib(tgen, "ipv6", dut, input_dict_summary, protocol=protocol)
assert (
result is True
- ), "Testcase {} : Failed" "Error: Routes is missing in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes is missing in RIB".format(tc_name)
step("Verify that show ip ospf summary should show the summaries.")
input_dict = {
@@ -2707,9 +2689,9 @@ def test_ospfv3_type5_summary_tc49_p2(request):
result = verify_ospf_summary(tgen, topo, dut, input_dict, ospf="ospf6")
assert (
result is True
- ), "Testcase {} : Failed" "Error: Summary missing in OSPF DB".format(tc_name)
+ ), "Testcase {} : Failed Error: Summary missing in OSPF DB".format(tc_name)
- step("Verify that originally advertised routes are withdraw from there" " peer.")
+ step("Verify that originally advertised routes are withdraw from there peer.")
input_dict = {
"r0": {"static_routes": [{"network": NETWORK["ipv6"], "next_hop": "blackhole"}]}
}
@@ -2717,7 +2699,7 @@ def test_ospfv3_type5_summary_tc49_p2(request):
result = verify_ospf6_rib(tgen, dut, input_dict, expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Error: " "Routes still present in OSPF RIB {}".format(
+ ), "Testcase {} : Failed \n Error: Routes still present in OSPF RIB {}".format(
tc_name, result
)
@@ -2726,7 +2708,7 @@ def test_ospfv3_type5_summary_tc49_p2(request):
)
assert (
result is not True
- ), "Testcase {} : Failed" "Error: Routes still present in RIB".format(tc_name)
+ ), "Testcase {} : Failed Error: Routes still present in RIB".format(tc_name)
write_test_footer(tc_name)
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py
index 2f90c98785..58608e249b 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_authentication.py
@@ -111,7 +111,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf6_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf6_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf6_covergence
)
@@ -182,7 +182,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -214,7 +214,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -244,7 +244,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=5
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -272,7 +272,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -287,7 +287,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
"show ip ospf6 neighbor cmd."
)
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut, expected=False)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -301,7 +301,7 @@ def test_ospf6_auth_trailer_tc1_md5(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -348,7 +348,7 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -380,7 +380,7 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -410,7 +410,7 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=5
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -438,7 +438,7 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -453,7 +453,7 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
"show ip ospf6 neighbor cmd."
)
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut, expected=False)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -467,7 +467,7 @@ def test_ospf6_auth_trailer_tc2_sha256(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -531,7 +531,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -561,7 +561,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -580,7 +580,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=5
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -606,7 +606,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -621,7 +621,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
"show ip ospf6 neighbor cmd."
)
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut, expected=False)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -635,7 +635,7 @@ def test_ospf6_auth_trailer_tc3_keychain_md5(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -699,7 +699,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -729,7 +729,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -748,7 +748,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=5
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -774,7 +774,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -789,7 +789,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
"show ip ospf6 neighbor cmd."
)
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut, expected=False)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -803,7 +803,7 @@ def test_ospf6_auth_trailer_tc4_keychain_sha256(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -850,7 +850,7 @@ def test_ospf6_auth_trailer_tc5_md5_keymissmatch(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -887,7 +887,7 @@ def test_ospf6_auth_trailer_tc5_md5_keymissmatch(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -919,7 +919,7 @@ def test_ospf6_auth_trailer_tc5_md5_keymissmatch(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -966,7 +966,7 @@ def test_ospf6_auth_trailer_tc6_sha256_mismatch(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -998,7 +998,7 @@ def test_ospf6_auth_trailer_tc6_sha256_mismatch(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -1030,7 +1030,7 @@ def test_ospf6_auth_trailer_tc6_sha256_mismatch(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -1102,7 +1102,7 @@ def test_ospf6_auth_trailer_tc7_keychain_md5_missmatch(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -1132,7 +1132,7 @@ def test_ospf6_auth_trailer_tc7_keychain_md5_missmatch(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -1162,7 +1162,7 @@ def test_ospf6_auth_trailer_tc7_keychain_md5_missmatch(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -1234,7 +1234,7 @@ def test_ospf6_auth_trailer_tc8_keychain_sha256_missmatch(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -1264,7 +1264,7 @@ def test_ospf6_auth_trailer_tc8_keychain_sha256_missmatch(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -1294,7 +1294,7 @@ def test_ospf6_auth_trailer_tc8_keychain_sha256_missmatch(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -1342,7 +1342,7 @@ def test_ospf6_auth_trailer_tc9_keychain_not_configured(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -1372,7 +1372,7 @@ def test_ospf6_auth_trailer_tc9_keychain_not_configured(request):
ospf6_covergence = verify_ospf6_neighbor(
tgen, topo, dut=dut, expected=False, retry_timeout=3
)
- assert ospf6_covergence is not True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is not True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
@@ -1402,7 +1402,7 @@ def test_ospf6_auth_trailer_tc10_no_auth_trailer(request):
dut = "r2"
ospf6_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf6_covergence is True, "Testcase {} :Failed \n Error:" " {}".format(
+ assert ospf6_covergence is True, "Testcase {} :Failed \n Error: {}".format(
tc_name, ospf6_covergence
)
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp.py
index 472364e84f..0c1e3fa43e 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp.py
@@ -115,7 +115,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf_covergence
)
@@ -257,7 +257,7 @@ def test_ospfv3_ecmp_tc16_p0(request):
step("Verify that OSPF is up with 8 neighborship sessions.")
dut = "r1"
ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
@@ -352,7 +352,7 @@ def test_ospfv3_ecmp_tc16_p0(request):
step("Verify that OSPF is up with 8 neighborship sessions.")
dut = "r1"
ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
@@ -390,7 +390,7 @@ def test_ospfv3_ecmp_tc17_p0(request):
step("Verify that OSPF is up with 2 neighborship sessions.")
dut = "r1"
ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp_lan.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp_lan.py
index 6b3e16965c..7c6773260e 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp_lan.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_ecmp_lan.py
@@ -128,7 +128,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf6_neighbor(tgen, topo, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf_covergence
)
@@ -282,7 +282,7 @@ def test_ospfv3_lan_ecmp_tc18_p0(request):
step("Verify that OSPF is up with 8 neighborship sessions.")
ospf_covergence = verify_ospf6_neighbor(tgen, topo, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
@@ -343,7 +343,7 @@ def test_ospfv3_lan_ecmp_tc18_p0(request):
dut = "r0"
ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
@@ -352,7 +352,7 @@ def test_ospfv3_lan_ecmp_tc18_p0(request):
dut = "r2"
ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut, lan=True)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_nssa.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_nssa.py
index c0d8d718cc..dc4ce88830 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_nssa.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_nssa.py
@@ -73,7 +73,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
result = verify_ospf6_neighbor(tgen, topo)
- assert result is True, "setup_module: Failed \n Error:" " {}".format(result)
+ assert result is True, "setup_module: Failed \n Error: {}".format(result)
logger.info("Running setup_module() done")
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_nssa2.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_nssa2.py
index 138112775f..90548fb5ce 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_nssa2.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_nssa2.py
@@ -138,7 +138,7 @@ def setup_module(mod):
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf_covergence
)
@@ -277,7 +277,7 @@ def test_ospfv3_nssa_tc26_p0(request):
result = verify_ospf6_neighbor(tgen, topo, dut="r2", expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Nbrs are not down" "Error: {}".format(tc_name, result)
+ ), "Testcase {} : Failed \n Nbrs are not down Error: {}".format(tc_name, result)
step("Now configure area 0 on interface of r1 connecting to r2.")
@@ -349,7 +349,7 @@ def test_ospfv3_nssa_tc26_p0(request):
result = verify_ospf6_neighbor(tgen, topo, dut="r2", expected=False)
assert (
result is not True
- ), "Testcase {} : Failed \n Nbrs are not down" "Error: {}".format(tc_name, result)
+ ), "Testcase {} : Failed \n Nbrs are not down Error: {}".format(tc_name, result)
step("Now configure area 2 on interface of r1 connecting to r2.")
@@ -471,7 +471,7 @@ def test_ospfv3_learning_tc15_p0(request):
result = verify_ospf6_neighbor(tgen, topo)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step("Change area 1 as non nssa area (on the fly changing area" " type on DUT).")
+ step("Change area 1 as non nssa area (on the fly changing area type on DUT).")
for rtr in ["r1", "r2", "r3"]:
input_dict = {
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_routemaps.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_routemaps.py
index b2cd0da24e..069806a3ef 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_routemaps.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_routemaps.py
@@ -129,7 +129,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf_covergence
)
@@ -212,9 +212,7 @@ def test_ospfv3_routemaps_functionality_tc19_p0(request):
result = create_router_ospf(tgen, topo, ospf_red_r1)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step(
- "Create prefix-list in R0 to permit 10.0.20.1/32 prefix &" " deny 10.0.20.2/32"
- )
+ step("Create prefix-list in R0 to permit 10.0.20.1/32 prefix & deny 10.0.20.2/32")
# Create ip prefix list
pfx_list = {
@@ -686,7 +684,7 @@ def test_ospfv3_routemaps_functionality_tc25_p0(request):
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
@@ -1078,7 +1076,7 @@ def test_ospfv3_routemaps_functionality_tc24_p0(request):
result = verify_prefix_lists(tgen, pfx_list)
assert (
result is not True
- ), "Testcase {} : Failed \n Prefix list not " "present. Error: {}".format(
+ ), "Testcase {} : Failed \n Prefix list not present. Error: {}".format(
tc_name, result
)
@@ -1147,7 +1145,7 @@ def test_ospfv3_routemaps_functionality_tc24_p0(request):
result = verify_prefix_lists(tgen, pfx_list)
assert (
result is not True
- ), "Testcase {} : Failed \n Prefix list not " "present. Error: {}".format(
+ ), "Testcase {} : Failed \n Prefix list not present. Error: {}".format(
tc_name, result
)
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_rte_calc.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_rte_calc.py
index 9e7f112efb..645dea8dec 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_rte_calc.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_rte_calc.py
@@ -120,7 +120,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf_covergence
)
@@ -256,7 +256,7 @@ def test_ospfv3_redistribution_tc5_p0(request):
step("Verify that OSPF neighbors are FULL.")
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
@@ -616,7 +616,7 @@ def test_ospfv3_redistribution_tc8_p1(request):
step("Verify that OSPF neighbours are reset and forms new adjacencies.")
# Api call verify whether OSPF is converged
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_single_area.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_single_area.py
index 5d5ba1480f..7199f160fe 100644
--- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_single_area.py
+++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_single_area.py
@@ -114,7 +114,7 @@ def setup_module(mod):
pytest.skip(tgen.errors)
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "setup_module :Failed \n Error: {}".format(
ospf_covergence
)
@@ -460,7 +460,7 @@ def test_ospfv3_hello_tc10_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
@@ -513,7 +513,7 @@ def test_ospfv3_hello_tc10_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
@@ -566,7 +566,7 @@ def test_ospfv3_hello_tc10_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
@@ -618,7 +618,7 @@ def test_ospfv3_hello_tc10_p0(request):
step("verify that ospf neighbours are full")
ospf_covergence = verify_ospf6_neighbor(tgen, topo, dut=dut)
- assert ospf_covergence is True, "setup_module :Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "Testcase Failed \n Error: {}".format(
ospf_covergence
)
@@ -709,9 +709,7 @@ def test_ospfv3_hello_tc10_p0(request):
result = create_interfaces_cfg(tgen, topo1)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step(
- "Verify that timer value is deleted from intf & " "set to default value 40 sec."
- )
+ step("Verify that timer value is deleted from intf & set to default value 40 sec.")
input_dict = {"r1": {"links": {"r0": {"ospf6": {"timerIntervalsConfigHello": 10}}}}}
dut = "r1"
result = verify_ospf6_interface(tgen, topo, dut=dut, input_dict=input_dict)
@@ -763,7 +761,7 @@ def test_ospfv3_dead_tc11_p0(request):
result = verify_ospf6_interface(tgen, topo, dut=dut, input_dict=input_dict)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step("modify dead interval from default value to r1" "dead interval timer on r2")
+ step("modify dead interval from default value to r1 dead interval timer on r2")
topo1 = {
"r0": {
@@ -799,7 +797,7 @@ def test_ospfv3_dead_tc11_p0(request):
# reconfiguring deleted ospf process by resetting the configs.
reset_config_on_routers(tgen)
- step("reconfigure the default dead interval timer value to " "default on r1 and r2")
+ step("reconfigure the default dead interval timer value to default on r1 and r2")
topo1 = {
"r0": {
"links": {
@@ -920,9 +918,7 @@ def test_ospfv3_dead_tc11_p0(request):
result = create_interfaces_cfg(tgen, topo1)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step(
- "Verify that timer value is deleted from intf & " "set to default value 40 sec."
- )
+ step("Verify that timer value is deleted from intf & set to default value 40 sec.")
input_dict = {"r1": {"links": {"r0": {"ospf6": {"timerIntervalsConfigDead": 40}}}}}
dut = "r1"
result = verify_ospf6_interface(tgen, topo, dut=dut, input_dict=input_dict)
@@ -967,18 +963,14 @@ def test_ospfv3_tc4_mtu_ignore_p0(request):
clear_ospf(tgen, "r0", ospf="ospf6")
clear_ospf(tgen, "r1", ospf="ospf6")
- step(
- "Verify that OSPF neighborship between R0 and R1 is stuck in Exstart" " State."
- )
+ step("Verify that OSPF neighborship between R0 and R1 is stuck in Exstart State.")
result = verify_ospf6_neighbor(tgen, topo, expected=False)
assert result is not True, (
"Testcase {} : Failed \n OSPF nbrs are Full "
"instead of Exstart. Error: {}".format(tc_name, result)
)
- step(
- "Verify that configured MTU value is updated in the show ip " "ospf interface."
- )
+ step("Verify that configured MTU value is updated in the show ip ospf interface.")
dut = "r0"
input_dict = {"r0": {"links": {"r1": {"ospf6": {"interfaceMtu": 1400}}}}}
@@ -1035,9 +1027,7 @@ def test_ospfv3_tc4_mtu_ignore_p0(request):
clear_ospf(tgen, "r0", ospf="ospf6")
- step(
- "Verify that OSPF neighborship between R0 and R1 is stuck in Exstart" " State."
- )
+ step("Verify that OSPF neighborship between R0 and R1 is stuck in Exstart State.")
result = verify_ospf6_neighbor(tgen, topo, expected=False)
assert result is not True, (
"Testcase {} : Failed \n OSPF nbrs are Full "
@@ -1054,9 +1044,7 @@ def test_ospfv3_tc4_mtu_ignore_p0(request):
result = verify_ospf6_neighbor(tgen, topo)
assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result)
- step(
- "Configure ospf interface with jumbo MTU (9216)." "Reset ospf neighbors on R0."
- )
+ step("Configure ospf interface with jumbo MTU (9216). Reset ospf neighbors on R0.")
rtr0.run("ifconfig {} mtu 9216".format(r0_r1_intf))
rtr1.run("ifconfig {} mtu 9216".format(r1_r0_intf))
@@ -1091,53 +1079,6 @@ def test_ospfv3_show_p1(request):
result = create_debug_log_config(tgen, input_dict)
- # Code coverage steps #Do Not upstream
- input_dict_config = {
- "r1": {
- "raw_config": [
- "end",
- "debug ospf6 event",
- "debug ospf6 gr helper",
- "debug ospf6 ism events",
- "debug ospf6 ism status",
- "debug ospf6 ism timers",
- "debug ospf6 nsm events",
- "debug ospf6 nsm status",
- "debug ospf6 nsm timers ",
- "debug ospf6 nssa",
- "debug ospf6 lsa aggregate",
- "debug ospf6 lsa flooding ",
- "debug ospf6 lsa generate",
- "debug ospf6 lsa install ",
- "debug ospf6 lsa refresh",
- "debug ospf6 packet all detail",
- "debug ospf6 packet all recv",
- "debug ospf6 packet all send",
- "debug ospf6 packet dd detail",
- "debug ospf6 packet dd recv",
- "debug ospf6 packet dd send ",
- "debug ospf6 packet hello detail",
- "debug ospf6 packet hello recv",
- "debug ospf6 packet hello send",
- "debug ospf6 packet ls-ack detail",
- "debug ospf6 packet ls-ack recv",
- "debug ospf6 packet ls-ack send",
- "debug ospf6 packet ls-request detail",
- "debug ospf6 packet ls-request recv",
- "debug ospf6 packet ls-request send",
- "debug ospf6 packet ls-update detail",
- "debug ospf6 packet ls-update recv",
- "debug ospf6 packet ls-update send",
- "debug ospf6 sr",
- "debug ospf6 te ",
- "debug ospf6 zebra interface",
- "debug ospf6 zebra redistribute",
- ]
- }
- }
-
- apply_raw_config(tgen, input_dict_config)
-
for rtr in topo["routers"]:
clear_ospf(tgen, rtr, ospf="ospf6")
@@ -1234,7 +1175,7 @@ def ospfv3_router_id_tc14_p2(request):
clear_ospf(tgen, rtr, ospf="ospf6")
ospf_covergence = verify_ospf6_neighbor(tgen, topo1)
- assert ospf_covergence is True, "OSPF NBRs not up.Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "OSPF NBRs not up.Failed \n Error: {}".format(
ospf_covergence
)
@@ -1260,9 +1201,9 @@ def ospfv3_router_id_tc14_p2(request):
assert result is True, "Testcase : Failed \n Error: {}".format(result)
ospf_covergence = verify_ospf6_neighbor(tgen, topo, expected=False)
- assert (
- ospf_covergence is not True
- ), "OSPF NBRs are up.Failed \n Error:" " {}".format(ospf_covergence)
+ assert ospf_covergence is not True, "OSPF NBRs are up.Failed \n Error: {}".format(
+ ospf_covergence
+ )
topo1 = {}
topo1 = deepcopy(topo)
@@ -1305,7 +1246,7 @@ def ospfv3_router_id_tc14_p2(request):
topo1["routers"]["r3"]["ospf6"]["router_id"] = "1.1.1.4"
ospf_covergence = verify_ospf6_neighbor(tgen, topo1)
- assert ospf_covergence is True, "OSPF NBRs not up.Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "OSPF NBRs not up.Failed \n Error: {}".format(
ospf_covergence
)
@@ -1313,7 +1254,7 @@ def ospfv3_router_id_tc14_p2(request):
reset_config_on_routers(tgen)
ospf_covergence = verify_ospf6_neighbor(tgen, topo)
- assert ospf_covergence is True, "OSPF NBRs not up.Failed \n Error:" " {}".format(
+ assert ospf_covergence is True, "OSPF NBRs not up.Failed \n Error: {}".format(
ospf_covergence
)
diff --git a/tests/topotests/pim_acl/r1/ospf_neighbor.json b/tests/topotests/pim_acl/r1/ospf_neighbor.json
index af83d6bea3..34ce481a3f 100644
--- a/tests/topotests/pim_acl/r1/ospf_neighbor.json
+++ b/tests/topotests/pim_acl/r1/ospf_neighbor.json
@@ -2,57 +2,57 @@
"neighbors":{
"192.168.0.11":[
{
- "priority":10,
+ "nbrPriority":10,
"converged":"Full",
- "address":"192.168.101.11",
+ "ifaceAddress":"192.168.101.11",
"ifaceName":"r1-eth1:192.168.101.1",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
],
"192.168.0.12":[
{
- "priority":0,
+ "nbrPriority":0,
"converged":"Full",
- "address":"192.168.101.12",
+ "ifaceAddress":"192.168.101.12",
"ifaceName":"r1-eth1:192.168.101.1",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
],
"192.168.0.13":[
{
- "priority":0,
+ "nbrPriority":0,
"converged":"Full",
- "address":"192.168.101.13",
+ "ifaceAddress":"192.168.101.13",
"ifaceName":"r1-eth1:192.168.101.1",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
],
"192.168.0.14":[
{
- "priority":0,
+ "nbrPriority":0,
"converged":"Full",
- "address":"192.168.101.14",
+ "ifaceAddress":"192.168.101.14",
"ifaceName":"r1-eth1:192.168.101.1",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
],
"192.168.0.15":[
{
- "priority":0,
+ "nbrPriority":0,
"converged":"Full",
- "address":"192.168.101.15",
+ "ifaceAddress":"192.168.101.15",
"ifaceName":"r1-eth1:192.168.101.1",
- "retransmitCounter":0,
- "requestCounter":0,
- "dbSummaryCounter":0
+ "linkStateRetransmissionListCounter":0,
+ "linkStateRequestListCounter":0,
+ "databaseSummaryListCounter":0
}
]
}
diff --git a/tests/topotests/pim_igmp_vrf/r1/ospf_blue_neighbor.json b/tests/topotests/pim_igmp_vrf/r1/ospf_blue_neighbor.json
index 1e70fcc36e..198098d3d3 100644
--- a/tests/topotests/pim_igmp_vrf/r1/ospf_blue_neighbor.json
+++ b/tests/topotests/pim_igmp_vrf/r1/ospf_blue_neighbor.json
@@ -4,9 +4,9 @@
"neighbors":{
"192.168.0.11":[
{
- "priority":10,
+ "nbrPriority":10,
"converged":"Full",
- "address":"192.168.101.11",
+ "ifaceAddress":"192.168.101.11",
"ifaceName":"r1-eth1:192.168.101.1"
}
]
diff --git a/tests/topotests/pim_igmp_vrf/r1/ospf_red_neighbor.json b/tests/topotests/pim_igmp_vrf/r1/ospf_red_neighbor.json
index 7f2ab248cc..6fce225ffc 100644
--- a/tests/topotests/pim_igmp_vrf/r1/ospf_red_neighbor.json
+++ b/tests/topotests/pim_igmp_vrf/r1/ospf_red_neighbor.json
@@ -4,9 +4,9 @@
"neighbors":{
"192.168.0.12":[
{
- "priority":10,
+ "nbrPriority":10,
"converged":"Full",
- "address":"192.168.101.12",
+ "ifaceAddress":"192.168.101.12",
"ifaceName":"r1-eth3:192.168.101.1"
}
]
diff --git a/tests/topotests/pytest.ini b/tests/topotests/pytest.ini
index 6986e3051c..f779bf0a74 100644
--- a/tests/topotests/pytest.ini
+++ b/tests/topotests/pytest.ini
@@ -1,6 +1,8 @@
# Skip pytests example directory
[pytest]
+asyncio_mode = auto
+
# We always turn this on inside conftest.py, default shown
# addopts = --junitxml=<rundir>/topotests.xml
@@ -24,7 +26,7 @@ log_file_date_format = %Y-%m-%d %H:%M:%S
junit_logging = all
junit_log_passing_tests = true
-norecursedirs = .git example_test example_topojson_test lib docker
+norecursedirs = .git example_munet example_test example_topojson_test lib munet docker
# Directory to store test results and run logs in, default shown
# rundir = /tmp/topotests
diff --git a/tests/topotests/rip_allow_ecmp/__init__.py b/tests/topotests/rip_allow_ecmp/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/rip_allow_ecmp/__init__.py
diff --git a/tests/topotests/rip_allow_ecmp/r1/frr.conf b/tests/topotests/rip_allow_ecmp/r1/frr.conf
new file mode 100644
index 0000000000..d8eb9a31d3
--- /dev/null
+++ b/tests/topotests/rip_allow_ecmp/r1/frr.conf
@@ -0,0 +1,9 @@
+!
+int r1-eth0
+ ip address 192.168.1.1/24
+!
+router rip
+ allow-ecmp
+ network 192.168.1.0/24
+ timers basic 5 15 10
+exit
diff --git a/tests/topotests/rip_allow_ecmp/r2/frr.conf b/tests/topotests/rip_allow_ecmp/r2/frr.conf
new file mode 100644
index 0000000000..d7ea6f3102
--- /dev/null
+++ b/tests/topotests/rip_allow_ecmp/r2/frr.conf
@@ -0,0 +1,13 @@
+!
+int lo
+ ip address 10.10.10.1/32
+!
+int r2-eth0
+ ip address 192.168.1.2/24
+!
+router rip
+ network 192.168.1.0/24
+ network 10.10.10.1/32
+ timers basic 5 15 10
+exit
+
diff --git a/tests/topotests/rip_allow_ecmp/r3/frr.conf b/tests/topotests/rip_allow_ecmp/r3/frr.conf
new file mode 100644
index 0000000000..2362c47b84
--- /dev/null
+++ b/tests/topotests/rip_allow_ecmp/r3/frr.conf
@@ -0,0 +1,13 @@
+!
+int lo
+ ip address 10.10.10.1/32
+!
+int r3-eth0
+ ip address 192.168.1.3/24
+!
+router rip
+ network 192.168.1.0/24
+ network 10.10.10.1/32
+ timers basic 5 15 10
+exit
+
diff --git a/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py b/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py
new file mode 100644
index 0000000000..acc0aea9e8
--- /dev/null
+++ b/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright (c) 2023 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+"""
+Test if RIP `allow-ecmp` command works correctly.
+"""
+
+import os
+import sys
+import json
+import pytest
+import functools
+
+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
+
+pytestmark = [pytest.mark.ripd]
+
+
+def setup_module(mod):
+ topodef = {"s1": ("r1", "r2", "r3")}
+ tgen = Topogen(topodef, mod.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+
+ for _, (rname, router) in enumerate(router_list.items(), 1):
+ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_rip_allow_ecmp():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+
+ def _show_rip_routes():
+ xpath = (
+ "/frr-ripd:ripd/instance[vrf='default']"
+ "/state/routes/route[prefix='10.10.10.1/32']"
+ )
+ try:
+ output = json.loads(
+ r1.vtysh_cmd(f"show yang operational-data {xpath} ripd")
+ )
+ except Exception:
+ return False
+
+ try:
+ output = output["frr-ripd:ripd"]["instance"][0]["state"]["routes"]
+ except KeyError:
+ return False
+
+ expected = {
+ "route": [
+ {
+ "prefix": "10.10.10.1/32",
+ "nexthops": {
+ "nexthop": [
+ {
+ "nh-type": "ip4",
+ "protocol": "rip",
+ "rip-type": "normal",
+ "gateway": "192.168.1.2",
+ "from": "192.168.1.2",
+ "tag": 0,
+ },
+ {
+ "nh-type": "ip4",
+ "protocol": "rip",
+ "rip-type": "normal",
+ "gateway": "192.168.1.3",
+ "from": "192.168.1.3",
+ "tag": 0,
+ },
+ ]
+ },
+ "metric": 2,
+ },
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_show_rip_routes)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+ assert result is None, "Can't see 10.10.10.1/32 as multipath in `show ip rip`"
+
+ def _show_routes():
+ output = json.loads(r1.vtysh_cmd("show ip route json"))
+ expected = {
+ "10.10.10.1/32": [
+ {
+ "nexthops": [
+ {
+ "ip": "192.168.1.2",
+ "active": True,
+ },
+ {
+ "ip": "192.168.1.3",
+ "active": True,
+ },
+ ]
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_show_routes)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+ assert result is None, "Can't see 10.10.10.1/32 as multipath in `show ip route`"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/rip_bfd_topo1/__init__.py b/tests/topotests/rip_bfd_topo1/__init__.py
new file mode 100755
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/__init__.py
diff --git a/tests/topotests/rip_bfd_topo1/r1/bfdd.conf b/tests/topotests/rip_bfd_topo1/r1/bfdd.conf
new file mode 100644
index 0000000000..e848bda89f
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r1/bfdd.conf
@@ -0,0 +1,6 @@
+bfd
+ profile slow
+ receive-interval 1000
+ transmit-interval 1000
+ exit
+exit
diff --git a/tests/topotests/rip_bfd_topo1/r1/ripd.conf b/tests/topotests/rip_bfd_topo1/r1/ripd.conf
new file mode 100644
index 0000000000..6cef84692a
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r1/ripd.conf
@@ -0,0 +1,17 @@
+interface r1-eth0
+ ip rip bfd
+ ip rip bfd profile slow
+exit
+!
+interface r1-eth1
+ ip rip bfd
+ ip rip bfd profile slow
+exit
+!
+router rip
+ allow-ecmp
+ network 192.168.0.1/24
+ network 192.168.1.1/24
+ redistribute connected
+ timers basic 10 40 30
+exit
diff --git a/tests/topotests/rip_bfd_topo1/r1/zebra.conf b/tests/topotests/rip_bfd_topo1/r1/zebra.conf
new file mode 100644
index 0000000000..e3800a9c68
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r1/zebra.conf
@@ -0,0 +1,11 @@
+interface r1-eth0
+ ip address 192.168.0.1/24
+exit
+!
+interface r1-eth1
+ ip address 192.168.1.1/24
+exit
+!
+interface lo
+ ip address 10.254.254.1/32
+exit
diff --git a/tests/topotests/rip_bfd_topo1/r2/bfdd.conf b/tests/topotests/rip_bfd_topo1/r2/bfdd.conf
new file mode 100644
index 0000000000..e848bda89f
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r2/bfdd.conf
@@ -0,0 +1,6 @@
+bfd
+ profile slow
+ receive-interval 1000
+ transmit-interval 1000
+ exit
+exit
diff --git a/tests/topotests/rip_bfd_topo1/r2/ripd.conf b/tests/topotests/rip_bfd_topo1/r2/ripd.conf
new file mode 100644
index 0000000000..35e4688c58
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r2/ripd.conf
@@ -0,0 +1,11 @@
+interface r2-eth0
+ ip rip bfd
+exit
+!
+router rip
+ bfd default-profile slow
+ network 192.168.0.2/24
+ redistribute connected
+ redistribute static
+ timers basic 10 40 30
+exit
diff --git a/tests/topotests/rip_bfd_topo1/r2/staticd.conf b/tests/topotests/rip_bfd_topo1/r2/staticd.conf
new file mode 100644
index 0000000000..6fe93748a0
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r2/staticd.conf
@@ -0,0 +1 @@
+ip route 10.254.254.100/32 lo
diff --git a/tests/topotests/rip_bfd_topo1/r2/zebra.conf b/tests/topotests/rip_bfd_topo1/r2/zebra.conf
new file mode 100644
index 0000000000..cad922f6bb
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r2/zebra.conf
@@ -0,0 +1,8 @@
+interface r2-eth0
+ ip address 192.168.0.2/24
+exit
+!
+interface lo
+ ip address 10.254.254.2/32
+exit
+
diff --git a/tests/topotests/rip_bfd_topo1/r3/bfdd.conf b/tests/topotests/rip_bfd_topo1/r3/bfdd.conf
new file mode 100644
index 0000000000..e848bda89f
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r3/bfdd.conf
@@ -0,0 +1,6 @@
+bfd
+ profile slow
+ receive-interval 1000
+ transmit-interval 1000
+ exit
+exit
diff --git a/tests/topotests/rip_bfd_topo1/r3/ripd.conf b/tests/topotests/rip_bfd_topo1/r3/ripd.conf
new file mode 100644
index 0000000000..0df0bac531
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r3/ripd.conf
@@ -0,0 +1,11 @@
+interface r3-eth0
+ ip rip bfd
+ ip rip bfd profile slow
+exit
+!
+router rip
+ network 192.168.1.2/24
+ redistribute connected
+ redistribute static
+ timers basic 10 40 30
+exit
diff --git a/tests/topotests/rip_bfd_topo1/r3/staticd.conf b/tests/topotests/rip_bfd_topo1/r3/staticd.conf
new file mode 100644
index 0000000000..6fe93748a0
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r3/staticd.conf
@@ -0,0 +1 @@
+ip route 10.254.254.100/32 lo
diff --git a/tests/topotests/rip_bfd_topo1/r3/zebra.conf b/tests/topotests/rip_bfd_topo1/r3/zebra.conf
new file mode 100644
index 0000000000..12ffeca42f
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/r3/zebra.conf
@@ -0,0 +1,7 @@
+interface r3-eth0
+ ip address 192.168.1.2/24
+exit
+!
+interface lo
+ ip address 10.254.254.3/32
+exit
diff --git a/tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.dot b/tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.dot
new file mode 100644
index 0000000000..1480a8f76d
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.dot
@@ -0,0 +1,58 @@
+## 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="rip_bfd_topo1";
+
+ # Routers
+ r1 [
+ shape=doubleoctagon,
+ label="r1",
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r2 [
+ shape=doubleoctagon
+ label="r2",
+ fillcolor="#f08080",
+ style=filled,
+ ];
+ r3 [
+ shape=doubleoctagon
+ label="r3",
+ fillcolor="#f08080",
+ style=filled,
+ ];
+
+ # Switches
+ s1 [
+ shape=oval,
+ label="s1\n192.168.0.0/24",
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+ s2 [
+ shape=oval,
+ label="s1\n192.168.1.0/24",
+ fillcolor="#d0e0d0",
+ style=filled,
+ ];
+
+ # Connections
+ r1 -- s1 [label="r1-eth0\n.1"];
+ r2 -- s1 [label="r2-eth0\n.2"];
+
+ r1 -- s2 [label="r1-eth1\n.1"];
+ r3 -- s2 [label="r1-eth0\n.2"];
+}
diff --git a/tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.png b/tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.png
new file mode 100644
index 0000000000..e5e362e3e0
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.png
Binary files differ
diff --git a/tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.py b/tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.py
new file mode 100644
index 0000000000..71c90931fb
--- /dev/null
+++ b/tests/topotests/rip_bfd_topo1/test_rip_bfd_topo1.py
@@ -0,0 +1,252 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# test_rip_bfd_topo1.py
+# Part of NetDEF Topology Tests
+#
+# Copyright (c) 2023 by
+# Network Device Education Foundation, Inc. ("NetDEF")
+#
+
+"""
+test_rip_bfd_topo1.py: Test RIP BFD integration.
+"""
+
+import sys
+import re
+import pytest
+
+from functools import partial
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter
+from lib.topolog import logger
+
+pytestmark = [
+ pytest.mark.bfdd,
+ pytest.mark.ripd,
+]
+
+
+@pytest.fixture(scope="module")
+def tgen(request):
+ "Setup/Teardown the environment and provide tgen argument to tests"
+
+ topodef = {
+ "s1": ("r1", "r2"),
+ "s2": ("r1", "r3")
+ }
+ tgen = Topogen(topodef, request.module.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+
+ for router_name, router in router_list.items():
+ router.load_config(TopoRouter.RD_BFD, "bfdd.conf")
+ router.load_config(TopoRouter.RD_RIP, "ripd.conf")
+ router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf")
+ if router_name in ["r2", "r3"]:
+ router.load_config(TopoRouter.RD_STATIC, "staticd.conf")
+
+ tgen.start_router()
+ yield tgen
+ tgen.stop_topology()
+
+
+@pytest.fixture(autouse=True)
+def skip_on_failure(tgen):
+ "Test if routers is still running otherwise skip tests"
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of previous test failure")
+
+
+def show_rip_json(router):
+ "Get router 'show ip rip' JSON output"
+ output = router.vtysh_cmd("show ip rip")
+ routes = output.splitlines()[6:]
+ json = {}
+
+ for route in routes:
+ match = re.match(
+ r"(.)\((.)\)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)", route)
+ if match is None:
+ continue
+
+ route_entry = {
+ "code": match[1],
+ "subCode": match[2],
+ "nextHop": match[4],
+ "metric": int(match[5]),
+ "from": match[6],
+ }
+
+ if json.get(match[3]) is None:
+ json[match[3]] = []
+
+ json[match[3]].append(route_entry)
+
+ return json
+
+
+def expect_routes(router, routes, time_amount):
+ "Expect 'routes' in 'router'."
+
+ def test_function():
+ "Internal test function."
+ return topotest.json_cmp(show_rip_json(router), routes)
+
+ _, result = topotest.run_and_expect(test_function,
+ None,
+ count=time_amount,
+ wait=1)
+ assert result is None, "Unexpected routing table in {}".format(
+ router.name)
+
+
+def expect_bfd_peers(router, peers):
+ "Expect 'peers' in 'router' BFD status."
+ test_func = partial(
+ topotest.router_json_cmp,
+ router,
+ "show bfd peers json",
+ peers,
+ )
+ _, result = topotest.run_and_expect(test_func, None, count=20, wait=1)
+ assert result is None, "{} BFD peer status mismatch".format(router)
+
+
+def test_rip_convergence(tgen):
+ "Test that RIP learns the neighbor routes."
+
+ expect_routes(
+ tgen.gears["r1"], {
+ "10.254.254.2/32": [{
+ "code": "R",
+ "subCode": "n",
+ "from": "192.168.0.2"
+ }],
+ "10.254.254.3/32": [{
+ "code": "R",
+ "subCode": "n",
+ "from": "192.168.1.2"
+ }],
+ "10.254.254.100/32": [{
+ "code": "R",
+ "subCode": "n",
+ "from": "192.168.0.2",
+ }, {
+ "code": "R",
+ "subCode": "n",
+ "from": "192.168.1.2",
+ }]
+ }, 40)
+
+ expect_bfd_peers(tgen.gears["r1"], [{
+ "peer": "192.168.0.2",
+ "status": "up",
+ "receive-interval": 1000,
+ "transmit-interval": 1000,
+ }, {
+ "peer": "192.168.1.2",
+ "status": "up",
+ "receive-interval": 1000,
+ "transmit-interval": 1000,
+ }])
+
+ expect_routes(
+ tgen.gears["r2"], {
+ "10.254.254.1/32": [{
+ "code": "R",
+ "subCode": "n",
+ "from": "192.168.0.1"
+ }],
+ "10.254.254.3/32": [{
+ "code": "R",
+ "subCode": "n",
+ "from": "192.168.0.1"
+ }],
+ "10.254.254.100/32": [{
+ "code": "S",
+ "subCode": "r",
+ "from": "self"
+ }]
+ }, 40)
+
+ expect_bfd_peers(tgen.gears["r2"], [{
+ "peer": "192.168.0.1",
+ "status": "up",
+ "receive-interval": 1000,
+ "transmit-interval": 1000,
+ }])
+
+ expect_routes(
+ tgen.gears["r3"], {
+ "10.254.254.1/32": [{
+ "code": "R",
+ "subCode": "n",
+ "from": "192.168.1.1"
+ }],
+ "10.254.254.2/32": [{
+ "code": "R",
+ "subCode": "n",
+ "from": "192.168.1.1"
+ }],
+ "10.254.254.100/32": [{
+ "code": "S",
+ "subCode": "r",
+ "from": "self"
+ }]
+ }, 40)
+
+ expect_bfd_peers(tgen.gears["r3"], [{
+ "peer": "192.168.1.1",
+ "status": "up",
+ "receive-interval": 1000,
+ "transmit-interval": 1000,
+ }])
+
+
+def test_rip_bfd_convergence(tgen):
+ "Test that RIP drop the gone neighbor routes."
+
+ tgen.gears["r3"].link_enable("r3-eth0", False)
+
+ expect_routes(
+ tgen.gears["r1"], {
+ "10.254.254.2/32": [{
+ "code": "R",
+ "subCode": "n",
+ "from": "192.168.0.2"
+ }],
+ "10.254.254.3/32": None,
+ "10.254.254.100/32": [{
+ "code": "R",
+ "subCode": "n",
+ "from": "192.168.0.2",
+ }]
+ }, 6)
+
+ expect_routes(
+ tgen.gears["r3"], {
+ "10.254.254.1/32": None,
+ "10.254.254.2/32": None,
+ "10.254.254.100/32": [{
+ "code": "S",
+ "subCode": "r",
+ "from": "self"
+ }]
+ }, 6)
+
+
+def test_memory_leak(tgen):
+ "Run the memory leak test and report results."
+
+ 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))
diff --git a/tests/topotests/rip_passive_interface/__init__.py b/tests/topotests/rip_passive_interface/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/rip_passive_interface/__init__.py
diff --git a/tests/topotests/rip_passive_interface/r1/frr.conf b/tests/topotests/rip_passive_interface/r1/frr.conf
new file mode 100644
index 0000000000..d8eb9a31d3
--- /dev/null
+++ b/tests/topotests/rip_passive_interface/r1/frr.conf
@@ -0,0 +1,9 @@
+!
+int r1-eth0
+ ip address 192.168.1.1/24
+!
+router rip
+ allow-ecmp
+ network 192.168.1.0/24
+ timers basic 5 15 10
+exit
diff --git a/tests/topotests/rip_passive_interface/r2/frr.conf b/tests/topotests/rip_passive_interface/r2/frr.conf
new file mode 100644
index 0000000000..d7ea6f3102
--- /dev/null
+++ b/tests/topotests/rip_passive_interface/r2/frr.conf
@@ -0,0 +1,13 @@
+!
+int lo
+ ip address 10.10.10.1/32
+!
+int r2-eth0
+ ip address 192.168.1.2/24
+!
+router rip
+ network 192.168.1.0/24
+ network 10.10.10.1/32
+ timers basic 5 15 10
+exit
+
diff --git a/tests/topotests/rip_passive_interface/r3/frr.conf b/tests/topotests/rip_passive_interface/r3/frr.conf
new file mode 100644
index 0000000000..2362c47b84
--- /dev/null
+++ b/tests/topotests/rip_passive_interface/r3/frr.conf
@@ -0,0 +1,13 @@
+!
+int lo
+ ip address 10.10.10.1/32
+!
+int r3-eth0
+ ip address 192.168.1.3/24
+!
+router rip
+ network 192.168.1.0/24
+ network 10.10.10.1/32
+ timers basic 5 15 10
+exit
+
diff --git a/tests/topotests/rip_passive_interface/test_rip_passive_interface.py b/tests/topotests/rip_passive_interface/test_rip_passive_interface.py
new file mode 100644
index 0000000000..c2b28c4a3e
--- /dev/null
+++ b/tests/topotests/rip_passive_interface/test_rip_passive_interface.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright (c) 2023 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+"""
+Test if RIP `passive-interface default` and `no passive-interface IFNAME`
+combination works as expected.
+"""
+
+import os
+import sys
+import json
+import pytest
+import functools
+
+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.common_config import step
+
+pytestmark = [pytest.mark.ripd]
+
+
+def setup_module(mod):
+ topodef = {"s1": ("r1", "r2", "r3")}
+ tgen = Topogen(topodef, mod.__name__)
+ tgen.start_topology()
+
+ router_list = tgen.routers()
+
+ for _, (rname, router) in enumerate(router_list.items(), 1):
+ router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_rip_passive_interface():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+
+ def _show_routes(nh_num):
+ output = json.loads(r1.vtysh_cmd("show ip route 10.10.10.1/32 json"))
+ expected = {
+ "10.10.10.1/32": [
+ {
+ "internalNextHopNum": nh_num,
+ "internalNextHopActiveNum": nh_num,
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_show_routes, 2)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+ assert result is None, "Got 10.10.10.1/32, but the next-hop count is not 2"
+
+ step("Configure `passive-interface default` on r2")
+ r2.vtysh_cmd(
+ """
+ configure terminal
+ router rip
+ passive-interface default
+ """
+ )
+
+ test_func = functools.partial(_show_routes, 1)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+ assert result is None, "Got 10.10.10.1/32, but the next-hop count is not 1"
+
+ step("Configure `no passive-interface r2-eth0` on r2 towards r1")
+ r2.vtysh_cmd(
+ """
+ configure terminal
+ router rip
+ no passive-interface r2-eth0
+ """
+ )
+
+ test_func = functools.partial(_show_routes, 2)
+ _, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
+ assert result is None, "Got 10.10.10.1/32, but the next-hop count is not 2"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/rip_topo1/r1/rip_status.ref b/tests/topotests/rip_topo1/r1/rip_status.ref
index 31ad46ab2e..19fff1a828 100644
--- a/tests/topotests/rip_topo1/r1/rip_status.ref
+++ b/tests/topotests/rip_topo1/r1/rip_status.ref
@@ -18,5 +18,5 @@ Routing Protocol is "rip"
r1-eth3
Routing Information Sources:
Gateway BadPackets BadRoutes Distance Last Update
- 193.1.1.2 0 0 120 XX:XX:XX
+ 193.1.1.2 0 0 120 XX:XX:XX
Distance: (default is 120)
diff --git a/tests/topotests/rip_topo1/r2/rip_status.ref b/tests/topotests/rip_topo1/r2/rip_status.ref
index 99841a62b0..468b7aece8 100644
--- a/tests/topotests/rip_topo1/r2/rip_status.ref
+++ b/tests/topotests/rip_topo1/r2/rip_status.ref
@@ -14,6 +14,6 @@ Routing Protocol is "rip"
193.1.2.0/24
Routing Information Sources:
Gateway BadPackets BadRoutes Distance Last Update
- 193.1.1.1 0 0 120 XX:XX:XX
- 193.1.2.2 0 0 120 XX:XX:XX
+ 193.1.1.1 0 0 120 XX:XX:XX
+ 193.1.2.2 0 0 120 XX:XX:XX
Distance: (default is 120)
diff --git a/tests/topotests/rip_topo1/r3/rip_status.ref b/tests/topotests/rip_topo1/r3/rip_status.ref
index 040d3c32a1..f423e83f24 100644
--- a/tests/topotests/rip_topo1/r3/rip_status.ref
+++ b/tests/topotests/rip_topo1/r3/rip_status.ref
@@ -12,5 +12,5 @@ Routing Protocol is "rip"
193.1.2.0/24
Routing Information Sources:
Gateway BadPackets BadRoutes Distance Last Update
- 193.1.2.1 0 0 120 XX:XX:XX
+ 193.1.2.1 0 0 120 XX:XX:XX
Distance: (default is 120)
diff --git a/tests/topotests/zebra_rib/test_zebra_rib.py b/tests/topotests/zebra_rib/test_zebra_rib.py
index 6137471ea6..e2863218b0 100644
--- a/tests/topotests/zebra_rib/test_zebra_rib.py
+++ b/tests/topotests/zebra_rib/test_zebra_rib.py
@@ -51,7 +51,8 @@ def config_macvlan(tgen, r_str, device, macvlan):
def setup_module(mod):
"Sets up the pytest environment"
- topodef = {"s1": ("r1", "r1", "r1", "r1", "r1", "r1", "r1", "r1")}
+ # 8 links to 8 switches on r1
+ topodef = {"s{}".format(x): ("r1",) for x in range(1, 9)}
tgen = Topogen(topodef, mod.__name__)
tgen.start_topology()