summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json34
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json34
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json36
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json28
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json34
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json30
-rw-r--r--tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json30
-rw-r--r--tests/topotests/bgp_bmp/r1import/frr.conf73
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-update-step1.json21
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json6
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json27
-rw-r--r--tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json6
-rw-r--r--tests/topotests/bgp_bmp/r3/frr.conf18
-rw-r--r--tests/topotests/bgp_bmp/test_bgp_bmp_3.py567
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py0
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf35
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf48
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py142
-rw-r--r--tests/topotests/munet/base.py28
-rw-r--r--tests/topotests/munet/munet-schema.json22
-rw-r--r--tests/topotests/munet/mutest/userapi.py2
-rw-r--r--tests/topotests/munet/native.py218
-rw-r--r--tests/topotests/munet/testing/util.py97
-rw-r--r--tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py16
27 files changed, 1630 insertions, 30 deletions
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json
new file mode 100644
index 0000000000..3542f4e495
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step1.json
@@ -0,0 +1,34 @@
+{
+ "loc-rib": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "is_filtered": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "444:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "is_filtered": false,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "555:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json
new file mode 100644
index 0000000000..60066d502c
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-loc-rib-step2.json
@@ -0,0 +1,34 @@
+{
+ "loc-rib": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "is_filtered": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "666:22",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "is_filtered": false,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "666:22",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json
new file mode 100644
index 0000000000..cf71f20485
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step1.json
@@ -0,0 +1,36 @@
+{
+ "post-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json
new file mode 100644
index 0000000000..b555c2a371
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-post-policy-step2.json
@@ -0,0 +1,36 @@
+{
+ "post-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json
new file mode 100644
index 0000000000..43273cc93a
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step1.json
@@ -0,0 +1,36 @@
+{
+ "pre-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json
new file mode 100644
index 0000000000..20549926d5
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-update-pre-policy-step2.json
@@ -0,0 +1,36 @@
+{
+ "pre-policy": {
+ "update": {
+ "172.31.0.77/32": {
+ "as_path": "",
+ "bgp_nexthop": "192.168.1.3",
+ "bmp_log_type": "update",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "as_path": "",
+ "bmp_log_type": "update",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "nxhp_ip": "192:167::3",
+ "origin": "IGP",
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "666:22",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json
new file mode 100644
index 0000000000..fcf518390d
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step1.json
@@ -0,0 +1,28 @@
+{
+ "loc-rib": {
+ "withdraw": {
+ "172.31.0.77/32": {
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.77/32",
+ "is_filtered": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "444:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1125/128",
+ "is_filtered": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "555:1",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json
new file mode 100644
index 0000000000..1e5040ba60
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-loc-rib-step2.json
@@ -0,0 +1,34 @@
+{
+ "loc-rib": {
+ "withdraw": {
+ "172.31.0.15/32": {
+ "afi": 1,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.15/32",
+ "is_filtered": false,
+ "label": 0,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "0:0",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "rd": "444:2",
+ "safi": 128
+ },
+ "2001::1111/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1111/128",
+ "is_filtered": false,
+ "label": 0,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.0.1",
+ "peer_distinguisher": "0:0",
+ "peer_type": "loc-rib instance",
+ "policy": "loc-rib",
+ "rd": "555:2",
+ "safi": 128
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json
new file mode 100644
index 0000000000..6626e91361
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-post-policy-step1.json
@@ -0,0 +1,30 @@
+{
+ "post-policy": {
+ "withdraw": {
+ "172.31.0.77/32": {
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "post-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json
new file mode 100644
index 0000000000..d3fb1b7ba1
--- /dev/null
+++ b/tests/topotests/bgp_bmp/bmp1import/bmp-withdraw-pre-policy-step1.json
@@ -0,0 +1,30 @@
+{
+ "pre-policy": {
+ "withdraw": {
+ "172.31.0.77/32": {
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "172.31.0.77/32",
+ "ipv6": false,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "444:1",
+ "peer_ip": "192.168.1.3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy"
+ },
+ "2001::1125/128": {
+ "afi": 2,
+ "bmp_log_type": "withdraw",
+ "ip_prefix": "2001::1125/128",
+ "ipv6": true,
+ "peer_asn": 65501,
+ "peer_bgp_id": "192.168.1.3",
+ "peer_distinguisher": "555:1",
+ "peer_ip": "192:167::3",
+ "peer_type": "route distinguisher instance",
+ "policy": "pre-policy",
+ "safi": 1
+ }
+ }
+ }
+}
diff --git a/tests/topotests/bgp_bmp/r1import/frr.conf b/tests/topotests/bgp_bmp/r1import/frr.conf
new file mode 100644
index 0000000000..bec4eb01c7
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/frr.conf
@@ -0,0 +1,73 @@
+interface r1import-eth0
+ ip address 192.0.2.1/24
+!
+interface r1import-eth1
+ ip address 192.168.0.1/24
+ ipv6 address 192:168::1/64
+!
+interface r1import-eth2
+ ip address 192.168.1.1/24
+ ipv6 address 192:167::1/64
+!
+router bgp 65501
+ bgp router-id 192.168.0.1
+ bgp log-neighbor-changes
+ no bgp ebgp-requires-policy
+ neighbor 192.168.0.2 remote-as 65502
+ neighbor 192:168::2 remote-as 65502
+!
+ bmp targets bmp1
+ bmp connect 192.0.2.10 port 1789 min-retry 100 max-retry 10000
+ bmp monitor ipv4 unicast pre-policy
+ bmp monitor ipv6 unicast pre-policy
+ bmp monitor ipv4 unicast post-policy
+ bmp monitor ipv6 unicast post-policy
+ bmp monitor ipv4 unicast loc-rib
+ bmp monitor ipv6 unicast loc-rib
+ bmp import-vrf-view vrf1
+ exit
+!
+ address-family ipv4 vpn
+ neighbor 192.168.0.2 activate
+ neighbor 192.168.0.2 soft-reconfiguration inbound
+ exit-address-family
+ address-family ipv6 vpn
+ neighbor 192:168::2 activate
+ neighbor 192:168::2 soft-reconfiguration inbound
+ exit-address-family
+ address-family ipv4 unicast
+ neighbor 192.168.0.2 activate
+ neighbor 192.168.0.2 soft-reconfiguration inbound
+ no neighbor 192:168::2 activate
+ exit-address-family
+!
+ address-family ipv6 unicast
+ neighbor 192:168::2 activate
+ neighbor 192:168::2 soft-reconfiguration inbound
+ exit-address-family
+!
+router bgp 65501 vrf vrf1
+ bgp router-id 192.168.0.1
+ bgp log-neighbor-changes
+ neighbor 192.168.1.3 remote-as 65501
+ neighbor 192:167::3 remote-as 65501
+ address-family ipv4 unicast
+ neighbor 192.168.1.3 activate
+ neighbor 192.168.1.3 soft-reconfiguration inbound
+ no neighbor 192:167::3 activate
+ label vpn export 101
+ rd vpn export 444:1
+ rt vpn both 52:100
+ export vpn
+ import vpn
+ exit-address-family
+ address-family ipv6 unicast
+ neighbor 192:167::3 activate
+ neighbor 192:167::3 soft-reconfiguration inbound
+ label vpn export 103
+ rd vpn export 555:1
+ rt vpn both 54:200
+ export vpn
+ import vpn
+ exit-address-family
+exit
diff --git a/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-update-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-update-step1.json
new file mode 100644
index 0000000000..c21a586c3b
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-update-step1.json
@@ -0,0 +1,21 @@
+{
+ "routes": {
+ "172.31.0.77/32": [
+ {
+ "bestpath": true,
+ "pathFrom": "internal",
+ "path": "",
+ "origin": "IGP",
+ "nexthops": [
+ {
+ "ip": "192.168.1.3",
+ "hostname": "r3",
+ "afi": "ipv4",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json
new file mode 100644
index 0000000000..154bef7995
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv4-withdraw-step1.json
@@ -0,0 +1,6 @@
+{
+ "routes": {
+ "172.31.0.77/32": null
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json
new file mode 100644
index 0000000000..14df5ec931
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-update-step1.json
@@ -0,0 +1,27 @@
+{
+ "routes": {
+ "2001::1125/128": [
+ {
+ "bestpath": true,
+ "pathFrom": "internal",
+ "path": "",
+ "origin": "IGP",
+ "nexthops": [
+ {
+ "ip": "192:167::3",
+ "hostname": "r3",
+ "afi": "ipv6",
+ "scope": "global"
+ },
+ {
+ "hostname": "r3",
+ "afi": "ipv6",
+ "scope": "link-local",
+ "used": true
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json
new file mode 100644
index 0000000000..7c7a95e33e
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r1import/show-bgp-vrf1-ipv6-withdraw-step1.json
@@ -0,0 +1,6 @@
+{
+ "routes": {
+ "2001::1125/128": null
+ }
+}
+
diff --git a/tests/topotests/bgp_bmp/r3/frr.conf b/tests/topotests/bgp_bmp/r3/frr.conf
new file mode 100644
index 0000000000..145e156b11
--- /dev/null
+++ b/tests/topotests/bgp_bmp/r3/frr.conf
@@ -0,0 +1,18 @@
+interface r3-eth0
+ ip address 192.168.1.3/24
+ ipv6 address 192:167::3/64
+!
+router bgp 65501
+ bgp router-id 192.168.1.3
+ bgp log-neighbor-changes
+ no bgp network import-check
+ neighbor 192.168.1.1 remote-as 65501
+ neighbor 192:167::1 remote-as 65501
+ address-family ipv4 unicast
+ neighbor 192.168.1.1 activate
+ no neighbor 192:167::1 activate
+ exit-address-family
+ address-family ipv6 unicast
+ neighbor 192:167::1 activate
+ exit-address-family
+exit
diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp_3.py b/tests/topotests/bgp_bmp/test_bgp_bmp_3.py
new file mode 100644
index 0000000000..212cf9e696
--- /dev/null
+++ b/tests/topotests/bgp_bmp/test_bgp_bmp_3.py
@@ -0,0 +1,567 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+# Copyright 2024 6WIND S.A.
+#
+
+"""
+test_bgp_bmp.py_3: Test BGP BMP functionalities
+
+ +------+ +------+ +------+
+ | | | | | |
+ | BMP1 |------------| R1 |---------------| R2 |
+ | | | | | |
+ +------+ +--+---+ +------+
+ |
+ +--+---+
+ | |
+ | R3 |
+ | |
+ +------+
+
+Setup two routers R1 and R2 with one link configured with IPv4 and
+IPv6 addresses.
+Configure BGP in R1 and R2 to exchange prefixes from
+the latter to the first router.
+Setup a link between R1 and the BMP server, activate the BMP feature in R1
+and ensure the monitored BGP sessions logs are well present on the BMP server.
+"""
+
+from functools import partial
+import json
+import os
+import pytest
+import sys
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join("../"))
+sys.path.append(os.path.join("../lib/"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.bgp import verify_bgp_convergence_from_running_config
+from lib.bgp import bgp_configure_prefixes
+from .bgpbmp import (
+ bmp_check_for_prefixes,
+ bmp_check_for_peer_message,
+ bmp_update_seq,
+ bmp_reset_seq,
+)
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+pytestmark = [pytest.mark.bgpd]
+
+PRE_POLICY = "pre-policy"
+POST_POLICY = "post-policy"
+LOC_RIB = "loc-rib"
+
+UPDATE_EXPECTED_JSON = False
+DEBUG_PCAP = False
+
+
+def build_topo(tgen):
+ tgen.add_router("r1import")
+ tgen.add_router("r2")
+ tgen.add_router("r3") # CPE behind r1
+
+ tgen.add_bmp_server("bmp1import", ip="192.0.2.10", defaultRoute="via 192.0.2.1")
+
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1import"])
+ switch.add_link(tgen.gears["bmp1import"])
+
+ tgen.add_link(tgen.gears["r1import"], tgen.gears["r2"], "r1import-eth1", "r2-eth0")
+ tgen.add_link(tgen.gears["r1import"], tgen.gears["r3"], "r1import-eth2", "r3-eth0")
+
+
+def setup_module(mod):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ tgen.net["r1import"].cmd(
+ """
+ip link add vrf1 type vrf table 10
+ip link set vrf1 up
+ip link set r1import-eth2 master vrf1
+ """
+ )
+
+ bmp_reset_seq()
+ if DEBUG_PCAP:
+ tgen.gears["r1import"].run("rm /tmp/bmp.pcap")
+ tgen.gears["r1import"].run(
+ "tcpdump -nni r1import-eth0 -s 0 -w /tmp/bmp.pcap &", stdout=None
+ )
+
+ for rname, router in tgen.routers().items():
+ logger.info("Loading router %s" % rname)
+ router.load_frr_config(
+ os.path.join(CWD, "{}/frr.conf".format(rname)),
+ [(TopoRouter.RD_ZEBRA, None), (TopoRouter.RD_BGP, "-M bmp")],
+ )
+
+ tgen.start_router()
+
+ logger.info("starting BMP servers")
+ for bmp_name, server in tgen.get_bmp_servers().items():
+ server.start(log_file=os.path.join(tgen.logdir, bmp_name, "bmp.log"))
+
+
+def teardown_module(_mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_bgp_convergence():
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ result = verify_bgp_convergence_from_running_config(tgen, dut="r1import")
+ assert result is True, "BGP is not converging"
+
+
+def _test_prefixes_syncro(policy, vrf=None, step=1):
+ """
+ Check that the given policy has syncronised the previously received BGP
+ updates.
+ """
+ tgen = get_topogen()
+
+ prefixes = ["172.31.0.77/32", "2001::1125/128"]
+ # check
+ test_func = partial(
+ bmp_check_for_prefixes,
+ prefixes,
+ "update",
+ policy,
+ step,
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import"),
+ tgen.gears["r1import"],
+ f"{CWD}/bmp1import",
+ UPDATE_EXPECTED_JSON,
+ LOC_RIB,
+ )
+ success, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert success, "Checking the updated prefixes has failed ! %s" % res
+
+
+def _test_prefixes(policy, vrf=None, step=0):
+ """
+ Setup the BMP monitor policy, Add and withdraw ipv4/v6 prefixes.
+ Check if the previous actions are logged in the BMP server with the right
+ message type and the right policy.
+ """
+ tgen = get_topogen()
+
+ safi = "vpn" if vrf else "unicast"
+
+ prefixes = ["172.31.0.77/32", "2001::1125/128"]
+
+ for type in ("update", "withdraw"):
+ bmp_update_seq(
+ tgen.gears["bmp1import"], os.path.join(tgen.logdir, "bmp1import", "bmp.log")
+ )
+
+ bgp_configure_prefixes(
+ tgen.gears["r3"],
+ 65501,
+ "unicast",
+ prefixes,
+ vrf=None,
+ update=(type == "update"),
+ )
+
+ logger.info(f"checking for prefixes {type}")
+
+ for ipver in [4, 6]:
+ if UPDATE_EXPECTED_JSON:
+ continue
+ ref_file = "{}/r1import/show-bgp-{}-ipv{}-{}-step{}.json".format(
+ CWD, vrf, ipver, type, step
+ )
+ expected = json.loads(open(ref_file).read())
+
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears["r1import"],
+ f"show bgp vrf {vrf} ipv{ipver} json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assertmsg = f"r1: BGP IPv{ipver} convergence failed"
+ assert res is None, assertmsg
+
+ # check
+ test_func = partial(
+ bmp_check_for_prefixes,
+ prefixes,
+ type,
+ policy,
+ step,
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import"),
+ tgen.gears["r1import"],
+ f"{CWD}/bmp1import",
+ UPDATE_EXPECTED_JSON,
+ LOC_RIB,
+ )
+ success, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert success, "Checking the updated prefixes has failed ! %s" % res
+
+
+def _test_peer_up(check_locrib=True):
+ """
+ Checking for BMP peers up messages
+ """
+
+ tgen = get_topogen()
+ if check_locrib:
+ peers = ["0.0.0.0", "192.168.1.3", "192:167::3"]
+ else:
+ peers = ["192.168.1.3", "192:167::3"]
+
+ logger.info("checking for BMP peers up messages")
+
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the updated prefixes has been failed !."
+
+
+def test_bmp_server_logging():
+ """
+ Assert the logging of the bmp server.
+ """
+
+ def check_for_log_file():
+ tgen = get_topogen()
+ output = tgen.gears["bmp1import"].run(
+ "ls {}".format(os.path.join(tgen.logdir, "bmp1import"))
+ )
+ if "bmp.log" not in output:
+ return False
+ return True
+
+ success, _ = topotest.run_and_expect(check_for_log_file, True, count=30, wait=1)
+ assert success, "The BMP server is not logging"
+
+
+def test_bmp_peer_up_start():
+ _test_peer_up()
+
+
+def test_bmp_bgp_unicast():
+ """
+ Add/withdraw bgp unicast prefixes and check the bmp logs.
+ """
+ logger.info("*** Unicast prefixes pre-policy logging ***")
+ _test_prefixes(PRE_POLICY, vrf="vrf1", step=1)
+ logger.info("*** Unicast prefixes post-policy logging ***")
+ _test_prefixes(POST_POLICY, vrf="vrf1", step=1)
+ logger.info("*** Unicast prefixes loc-rib logging ***")
+ _test_prefixes(LOC_RIB, vrf="vrf1", step=1)
+
+
+def test_peer_down():
+ """
+ Checking for BMP peers down messages
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r3"].vtysh_cmd("clear bgp *")
+
+ peers = ["192.168.1.3", "192:167::3"]
+
+ logger.info("checking for BMP peers down messages")
+
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the updated prefixes has been failed !."
+
+
+def test_reconfigure_prefixes():
+ """
+ Reconfigured BGP networks from R3. Check for BGP VRF update messages
+ """
+
+ tgen = get_topogen()
+
+ prefixes = ["172.31.0.77/32", "2001::1125/128"]
+ bgp_configure_prefixes(
+ tgen.gears["r3"],
+ 65501,
+ "unicast",
+ prefixes,
+ vrf=None,
+ update=True,
+ )
+
+ for ipver in [4, 6]:
+ ref_file = "{}/r1import/show-bgp-{}-ipv{}-{}-step{}.json".format(
+ CWD, "vrf1", ipver, "update", 1
+ )
+ expected = json.loads(open(ref_file).read())
+
+ test_func = partial(
+ topotest.router_json_cmp,
+ tgen.gears["r1import"],
+ f"show bgp vrf vrf1 ipv{ipver} json",
+ expected,
+ )
+ _, res = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assertmsg = f"r1: BGP IPv{ipver} convergence failed"
+ assert res is None, assertmsg
+
+
+def test_monitor_syncro():
+ """
+ Checking for BMP peers down messages
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501
+ bmp targets bmp1
+ bmp import-vrf-view vrf1
+ """
+ )
+
+ logger.info("*** Unicast prefixes pre-policy logging ***")
+ _test_prefixes_syncro(PRE_POLICY, vrf="vrf1")
+ logger.info("*** Unicast prefixes post-policy logging ***")
+ _test_prefixes_syncro(POST_POLICY, vrf="vrf1")
+ logger.info("*** Unicast prefixes loc-rib logging ***")
+ _test_prefixes_syncro(LOC_RIB, vrf="vrf1")
+
+
+def test_reconfigure_route_distinguisher_vrf1():
+ """
+ Checking for BMP peers down messages
+ """
+ tgen = get_topogen()
+
+ bmp_update_seq(
+ tgen.gears["bmp1import"], os.path.join(tgen.logdir, "bmp1import", "bmp.log")
+ )
+ peers = ["0.0.0.0"]
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501 vrf vrf1
+ address-family ipv4 unicast
+ rd vpn export 666:22
+ exit-address-family
+ address-family ipv6 unicast
+ rd vpn export 666:22
+ """
+ )
+ logger.info(
+ "Checking for BMP peer down LOC-RIB message with route-distinguisher set to 444:1"
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_distinguisher="444:1",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer down LOC-RIB message with route-distinguisher set to 444:1 failed !."
+
+ logger.info(
+ "Checking for BMP peer up LOC-RIB messages with route-distinguisher set to 666:22"
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_distinguisher="666:22",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer up LOC-RIB message with route-distinguisher set to 666:22 failed !."
+
+ logger.info(
+ "Checking for BMP peer up messages with route-distinguisher set to 666:22"
+ )
+ peers = ["192.168.1.3", "192:167::3"]
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_distinguisher="666:22",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer up messages with route-distinguisher set to 666:22 failed !."
+
+ logger.info("*** Unicast prefixes pre-policy logging ***")
+ _test_prefixes_syncro(PRE_POLICY, vrf="vrf1", step=2)
+ logger.info("*** Unicast prefixes post-policy logging ***")
+ _test_prefixes_syncro(POST_POLICY, vrf="vrf1", step=2)
+ logger.info("*** Unicast prefixes loc-rib logging ***")
+ _test_prefixes_syncro(LOC_RIB, vrf="vrf1", step=2)
+
+
+def test_bgp_routerid_changed():
+ """
+ Checking for BGP loc-rib up messages with new router-id
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501 vrf vrf1
+ bgp router-id 192.168.1.77
+ """
+ )
+
+ peers = ["0.0.0.0"]
+
+ logger.info(
+ "checking for BMP peer down LOC-RIB message with router-id set to 192.168.0.1."
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_bgp_id="192.168.0.1",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer down LOC-RIB message with router-id set to 192.168.0.1 failed !."
+
+ logger.info(
+ "checking for BMP peer up LOC-RIB message with router-id set to 192.168.1.77."
+ )
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ peer_bgp_id="192.168.1.77",
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert (
+ success
+ ), "Checking the BMP peer up LOC-RIB message with router-id set to 192.168.1.77 failed !."
+
+
+def test_bgp_instance_flapping():
+ """
+ Checking for BGP loc-rib up messages
+ """
+ tgen = get_topogen()
+
+ # create flapping at BMP
+ # note: only peer up are handled at BMP level today
+ tgen.net["r1import"].cmd("ip link set dev vrf1 down")
+
+ peers = ["0.0.0.0"]
+
+ logger.info("checking for BMP peer down LOC-RIB message.")
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the BMP peer down LOC-RIB message failed !."
+
+ tgen.net["r1import"].cmd("ip link set dev vrf1 up")
+
+ logger.info("checking for BMP peer up LOC-RIB message.")
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer up",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ is_rd_instance=True,
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the BMP peer up LOC-RIB message failed !."
+
+
+def test_peer_up_after_flush():
+ """
+ Checking for BMP peers down messages
+ """
+ _test_peer_up(check_locrib=False)
+
+
+def test_peer_down_locrib():
+ """
+ Checking for BMP peers down loc-rib messages
+ """
+ tgen = get_topogen()
+
+ tgen.gears["r1import"].vtysh_cmd(
+ """
+ configure terminal
+ router bgp 65501
+ bmp targets bmp1
+ no bmp import-vrf-view vrf1
+ """
+ )
+
+ peers = ["0.0.0.0"]
+
+ logger.info("checking for BMP peers down messages")
+
+ test_func = partial(
+ bmp_check_for_peer_message,
+ peers,
+ "peer down",
+ tgen.gears["bmp1import"],
+ os.path.join(tgen.logdir, "bmp1import", "bmp.log"),
+ )
+ success, _ = topotest.run_and_expect(test_func, True, count=30, wait=1)
+ assert success, "Checking the BMP peer down message has failed !."
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf
new file mode 100644
index 0000000000..428b1d992f
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf
@@ -0,0 +1,35 @@
+!
+interface r1-eth0
+ ip address 192.168.179.4/24
+exit
+!
+router bgp 65001
+!
+router bgp 65001 vrf CUSTOMER-A
+ bgp router-id 192.168.179.4
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.179.5 remote-as external
+!
+ address-family ipv4 unicast
+ neighbor 192.168.179.5 next-hop-self
+ neighbor 192.168.179.5 allowas-in 10
+ label vpn export auto
+ rd vpn export 100:1
+ rt vpn both 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+router bgp 65001 vrf CUSTOMER-B
+ bgp router-id 192.168.0.1
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+!
+ address-family ipv4 unicast
+ label vpn export auto
+ rd vpn export 100:2
+ rt vpn import 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf
new file mode 100644
index 0000000000..58e63d6cf0
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf
@@ -0,0 +1,48 @@
+!
+interface lo
+ ip address 10.10.10.10/32
+!
+interface r2-eth0
+ ip address 192.168.179.5/24
+exit
+!
+interface r2-eth1
+ ip address 192.168.2.2/24
+exit
+!
+router bgp 65002
+!
+router bgp 65002 vrf CUSTOMER-A
+ bgp router-id 192.168.179.5
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.179.4 remote-as external
+!
+ address-family ipv4 unicast
+ neighbor 192.168.179.4 next-hop-self
+ neighbor 192.168.179.4 route-map r1 out
+ label vpn export auto
+ rd vpn export 100:1
+ rt vpn import 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+router bgp 65002 vrf CUSTOMER-B
+ bgp router-id 192.168.0.2
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+!
+ address-family ipv4 unicast
+ redistribute connected
+ network 10.10.10.10/32
+ label vpn export auto
+ rd vpn export 100:2
+ rt vpn both 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+route-map r1 permit 10
+ set as-path prepend 65001
+!
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py
new file mode 100644
index 0000000000..23325c7a17
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# Copyright (c) 2024 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+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, get_topogen
+
+pytestmark = [pytest.mark.bgpd]
+
+
+def build_topo(tgen):
+ tgen.add_router("r1")
+ tgen.add_router("r2")
+
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r2"])
+
+ switch = tgen.add_switch("s2")
+ switch.add_link(tgen.gears["r1"])
+
+ switch = tgen.add_switch("s3")
+ switch.add_link(tgen.gears["r2"])
+
+
+def setup_module(mod):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+
+ r1.run("ip link add CUSTOMER-A type vrf table 1001")
+ r1.run("ip link set up dev CUSTOMER-A")
+ r1.run("ip link set r1-eth0 master CUSTOMER-A")
+
+ r1.run("ip link add CUSTOMER-B type vrf table 1002")
+ r1.run("ip link set up dev CUSTOMER-B")
+ r1.run("ip link set r1-eth1 master CUSTOMER-B")
+
+ r2.run("ip link add CUSTOMER-A type vrf table 1001")
+ r2.run("ip link set up dev CUSTOMER-A")
+ r2.run("ip link set r2-eth0 master CUSTOMER-A")
+
+ r2.run("ip link add CUSTOMER-B type vrf table 1002")
+ r2.run("ip link set up dev CUSTOMER-B")
+ r2.run("ip link set r2-eth1 master CUSTOMER-B")
+
+ 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_bgp_vpnv4_import_allowas_in_between_vrf():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+
+ def _bgp_converge():
+ output = json.loads(
+ r1.vtysh_cmd("show bgp vrf CUSTOMER-A ipv4 unicast 10.10.10.10/32 json")
+ )
+ expected = {
+ "paths": [
+ {
+ "aspath": {
+ "string": "65002 65001",
+ },
+ "valid": True,
+ }
+ ]
+ }
+ 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 to see 10.10.10.10/32 with a valid next-hop"
+
+ def _vrf_route_imported_to_vrf():
+ output = json.loads(
+ r1.vtysh_cmd("show ip route vrf CUSTOMER-B 10.10.10.10/32 json")
+ )
+ expected = {
+ "10.10.10.10/32": [
+ {
+ "protocol": "bgp",
+ "vrfName": "CUSTOMER-B",
+ "selected": True,
+ "installed": True,
+ "table": 1002,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": True,
+ "ip": "192.168.179.5",
+ "afi": "ipv4",
+ "interfaceName": "r1-eth0",
+ "vrf": "CUSTOMER-A",
+ "active": True,
+ }
+ ],
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_vrf_route_imported_to_vrf)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert (
+ result is None
+ ), "Failed to see 10.10.10.10/32 to be imported into CUSTOMER-B VRF (Zebra)"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/munet/base.py b/tests/topotests/munet/base.py
index e77eb15dc8..e9410d442d 100644
--- a/tests/topotests/munet/base.py
+++ b/tests/topotests/munet/base.py
@@ -332,6 +332,10 @@ class Commander: # pylint: disable=R0904
self.last = None
self.exec_paths = {}
+ # For running commands one time only (deals with asyncio)
+ self.cmd_once_done = {}
+ self.cmd_once_locks = {}
+
if not logger:
logname = f"munet.{self.__class__.__name__.lower()}.{name}"
self.logger = logging.getLogger(logname)
@@ -1189,7 +1193,7 @@ class Commander: # pylint: disable=R0904
return stdout
# Run a command in a new window (gnome-terminal, screen, tmux, xterm)
- def run_in_window(
+ def run_in_window( # pylint: disable=too-many-positional-arguments
self,
cmd,
wait_for=False,
@@ -1205,7 +1209,7 @@ class Commander: # pylint: disable=R0904
Args:
cmd: string to execute.
- wait_for: True to wait for exit from command or `str` as channel neme to
+ wait_for: True to wait for exit from command or `str` as channel name to
signal on exit, otherwise False
background: Do not change focus to new window.
title: Title for new pane (tmux) or window (xterm).
@@ -1405,6 +1409,26 @@ class Commander: # pylint: disable=R0904
return pane_info
+ async def async_cmd_raises_once(self, cmd, **kwargs):
+ if cmd in self.cmd_once_done:
+ return self.cmd_once_done[cmd]
+
+ if cmd not in self.cmd_once_locks:
+ self.cmd_once_locks[cmd] = asyncio.Lock()
+
+ async with self.cmd_once_locks[cmd]:
+ if cmd not in self.cmd_once_done:
+ self.logger.info("Running command once: %s", cmd)
+ self.cmd_once_done[cmd] = await commander.async_cmd_raises(
+ cmd, **kwargs
+ )
+ return self.cmd_once_done[cmd]
+
+ def cmd_raises_once(self, cmd, **kwargs):
+ if cmd not in self.cmd_once_done:
+ self.cmd_once_done[cmd] = commander.cmd_raises(cmd, **kwargs)
+ return self.cmd_once_done[cmd]
+
def delete(self):
"""Calls self.async_delete within an exec loop."""
asyncio.run(self.async_delete())
diff --git a/tests/topotests/munet/munet-schema.json b/tests/topotests/munet/munet-schema.json
index 6ebc368dcb..44453cb44f 100644
--- a/tests/topotests/munet/munet-schema.json
+++ b/tests/topotests/munet/munet-schema.json
@@ -117,6 +117,12 @@
"bios": {
"type": "string"
},
+ "cloud-init": {
+ "type": "boolean"
+ },
+ "cloud-init-disk": {
+ "type": "string"
+ },
"disk": {
"type": "string"
},
@@ -129,7 +135,7 @@
"initial-cmd": {
"type": "string"
},
- "kerenel": {
+ "kernel": {
"type": "string"
},
"initrd": {
@@ -373,6 +379,12 @@
"networks-autonumber": {
"type": "boolean"
},
+ "initial-setup-cmd": {
+ "type": "string"
+ },
+ "initial-setup-host-cmd": {
+ "type": "string"
+ },
"networks": {
"type": "array",
"items": {
@@ -452,6 +464,12 @@
"bios": {
"type": "string"
},
+ "cloud-init": {
+ "type": "boolean"
+ },
+ "cloud-init-disk": {
+ "type": "string"
+ },
"disk": {
"type": "string"
},
@@ -464,7 +482,7 @@
"initial-cmd": {
"type": "string"
},
- "kerenel": {
+ "kernel": {
"type": "string"
},
"initrd": {
diff --git a/tests/topotests/munet/mutest/userapi.py b/tests/topotests/munet/mutest/userapi.py
index abc63af365..e367e65a15 100644
--- a/tests/topotests/munet/mutest/userapi.py
+++ b/tests/topotests/munet/mutest/userapi.py
@@ -180,7 +180,7 @@ class TestCase:
# sum_hfmt = "{:5.5s} {:4.4s} {:>6.6s} {}"
# sum_dfmt = "{:5s} {:4.4s} {:^6.6s} {}"
- sum_fmt = "%-8.8s %4.4s %{}s %6s %s"
+ sum_fmt = "%-10s %4.4s %{}s %6s %s"
def __init__(
self,
diff --git a/tests/topotests/munet/native.py b/tests/topotests/munet/native.py
index e3b782396e..4e29fe91b1 100644
--- a/tests/topotests/munet/native.py
+++ b/tests/topotests/munet/native.py
@@ -24,6 +24,13 @@ import time
from pathlib import Path
+
+try:
+ # We only want to require yaml for the gen cloud image feature
+ import yaml
+except ImportError:
+ pass
+
from . import cli
from .base import BaseMunet
from .base import Bridge
@@ -749,9 +756,11 @@ class L3NodeMixin(NodeMixin):
# 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")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=0")
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.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=1")
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")
@@ -2265,6 +2274,164 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
tid = self.cpu_thread_map[i]
self.cmd_raises_nsonly(f"taskset -cp {aff} {tid}")
+ def _gen_network_config(self):
+ intfs = sorted(self.intfs)
+ if not intfs:
+ return ""
+
+ self.logger.debug("Generating cloud-init interface config")
+ config = {}
+ config["version"] = 2
+ enets = config["ethernets"] = {}
+
+ for ifname in sorted(self.intfs):
+ self.logger.debug("Interface %s", ifname)
+ conn = find_with_kv(self.config["connections"], "name", ifname)
+
+ index = self.config["connections"].index(conn)
+ to = conn["to"]
+ switch = self.unet.switches.get(to)
+ mtu = conn.get("mtu")
+ if not mtu and switch:
+ mtu = switch.config.get("mtu")
+
+ devaddr = conn.get("physical", "")
+ # Eventually we should get the MAC from /sys
+ if not devaddr:
+ mac = self.tapmacs.get(ifname, f"02:aa:aa:aa:{index:02x}:{self.id:02x}")
+ nic = {
+ "match": {"macaddress": str(mac)},
+ "set-name": ifname,
+ }
+ if mtu:
+ nic["mtu"] = str(mtu)
+ enets[f"nic-{ifname}"] = nic
+
+ ifaddr4 = self.get_intf_addr(ifname, ipv6=False)
+ ifaddr6 = self.get_intf_addr(ifname, ipv6=True)
+ if not ifaddr4 and not ifaddr6:
+ continue
+ net = {
+ "dhcp4": False,
+ "dhcp6": False,
+ "accept-ra": False,
+ "addresses": [],
+ }
+ if ifaddr4:
+ net["addresses"].append(str(ifaddr4))
+ if ifaddr6:
+ net["addresses"].append(str(ifaddr6))
+ if switch and hasattr(switch, "is_nat") and switch.is_nat:
+ net["nameservers"] = {"addresses": []}
+ nameservers = net["nameservers"]["addresses"]
+ if hasattr(switch, "ip6_address"):
+ net["gateway6"] = str(switch.ip6_address)
+ nameservers.append("2001:4860:4860::8888")
+ if switch.ip_address:
+ net["gateway4"] = str(switch.ip_address)
+ nameservers.append("8.8.8.8")
+ enets[ifname] = net
+
+ return yaml.safe_dump(config)
+
+ def _gen_cloud_init(self):
+ qc = self.qemu_config
+ cc = qc.get("console", {})
+ cipath = self.rundir.joinpath("cloud-init.img")
+
+ geniso = get_exec_path_host("genisoimage")
+ if not geniso:
+ mfbin = get_exec_path_host("mkfs.vfat")
+ mcbin = get_exec_path_host("mcopy")
+ assert (
+ mfbin and mcbin
+ ), "genisoimage or mkfs.vfat,mcopy needed to gen cloud-init disk"
+
+ #
+ # cloud-init: meta-data
+ #
+ mdata = f"""
+instance-id: "munet-{self.id}"
+local-hostname: "{self.name}"
+"""
+ #
+ # cloud-init: user-data
+ #
+ ssh_auth_s = ""
+ if bool(self.ssh_keyfile):
+ pubkey = commander.cmd_raises(f"ssh-keygen -y -f {self.ssh_keyfile}")
+ assert pubkey, f"Can't extract public key from {self.ssh_keyfile}"
+ pubkey = pubkey.strip()
+ ssh_auth_s = f'ssh_authorized_keys: ["{pubkey}"]'
+
+ user = cc.get("user", "root")
+ password = cc.get("password", "admin")
+ if user != "root":
+ root_password = "admin"
+ else:
+ root_password = password
+
+ udata = f"""#cloud-config
+disable_root: 0
+ssh_pwauth: 1
+hostname: {self.name}
+runcmd:
+ - systemctl enable serial-getty@ttyS1.service
+ - systemctl start serial-getty@ttyS1.service
+ - systemctl enable serial-getty@ttyS2.service
+ - systemctl start serial-getty@ttyS2.service
+ - systemctl enable serial-getty@hvc0.service
+ - systemctl start serial-getty@hvc0.service
+ - systemctl enable serial-getty@hvc1.service
+ - systemctl start serial-getty@hvc1.service
+users:
+ - name: root
+ lock_passwd: false
+ plain_text_passwd: "{root_password}"
+ {ssh_auth_s}
+"""
+ if user != "root":
+ udata += """
+ - name: {user}
+ lock_passwd: false
+ plain_text_passwd: "{password}"
+ {ssh_auth_s}
+"""
+ #
+ # cloud-init: network-config
+ #
+ ndata = self._gen_network_config()
+
+ #
+ # Generate cloud-init files
+ #
+ cidir = self.rundir.joinpath("ci-data")
+ commander.cmd_raises(f"mkdir -p {cidir}")
+
+ with open(cidir.joinpath("meta-data"), "w+", encoding="utf-8") as f:
+ f.write(mdata)
+ with open(cidir.joinpath("user-data"), "w+", encoding="utf-8") as f:
+ f.write(udata)
+ files = "meta-data user-data"
+ if ndata:
+ files += " network-config"
+ with open(cidir.joinpath("network-config"), "w+", encoding="utf-8") as f:
+ f.write(ndata)
+ if geniso:
+ commander.cmd_raises(
+ f"cd {cidir} && "
+ f'genisoimage -output "{cipath}" -volid cidata'
+ f" -joliet -rock {files}"
+ )
+ else:
+ commander.cmd_raises(f'cd {cidir} && mkfs.vfat -n cidata "{cipath}"')
+ commander.cmd_raises(f'cd {cidir} && mcopy -oi "{cipath}" {files}')
+
+ #
+ # Generate cloud-init disk
+ #
+ return cipath
+
async def launch(self):
"""Launch qemu."""
self.logger.info("%s: Launch Qemu", self)
@@ -2367,11 +2534,21 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
diskpath = os.path.join(self.unet.config_dirname, diskpath)
if dtpl and (not disk or not os.path.exists(diskpath)):
+ basename = os.path.basename(dtpl)
+ confdir = self.unet.config_dirname
+ if re.match("(https|http|ftp|tftp):.*", dtpl):
+ await self.unet.async_cmd_raises_once(
+ f"cd {confdir} && (test -e {basename} || curl -fLO {dtpl})"
+ )
+ dtplpath = os.path.join(confdir, basename)
+
if not disk:
- disk = qc["disk"] = f"{self.name}-{os.path.basename(dtpl)}"
+ disk = qc["disk"] = f"{self.name}-{basename}"
diskpath = os.path.join(self.rundir, disk)
+
if self.path_exists(diskpath):
logging.debug("Disk '%s' file exists, using.", diskpath)
+
else:
if dtplpath[0] != "/":
dtplpath = os.path.join(self.unet.config_dirname, dtpl)
@@ -2392,11 +2569,15 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
args.extend(["-device", "ahci,id=ahci"])
args.extend(["-device", "ide-hd,bus=ahci.0,drive=sata-disk0"])
- cidiskpath = qc.get("cloud-init-disk")
- if cidiskpath:
- if cidiskpath[0] != "/":
- cidiskpath = os.path.join(self.unet.config_dirname, cidiskpath)
- args.extend(["-drive", f"file={cidiskpath},if=virtio,format=qcow2"])
+ if qc.get("cloud-init"):
+ cidiskpath = qc.get("cloud-init-disk")
+ if cidiskpath:
+ if cidiskpath[0] != "/":
+ cidiskpath = os.path.join(self.unet.config_dirname, cidiskpath)
+ else:
+ cidiskpath = self._gen_cloud_init()
+ diskfmt = "qcow2" if str(cidiskpath).endswith("qcow2") else "raw"
+ args.extend(["-drive", f"file={cidiskpath},if=virtio,format={diskfmt}"])
# args.extend(["-display", "vnc=0.0.0.0:40"])
@@ -2488,7 +2669,7 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
if use_cmdcon:
confiles.append("_cmdcon")
- password = cc.get("password", "")
+ password = cc.get("password", "admin")
if self.disk_created:
password = cc.get("initial-password", password)
@@ -2764,9 +2945,11 @@ ff02::2\tip6-allrouters
# 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")
+ self.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=0")
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.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=1")
# 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.
@@ -2774,6 +2957,24 @@ ff02::2\tip6-allrouters
# # Let's hide podman details
# self.tmpfs_mount("/var/lib/containers/storage/overlay-containers")
+ def run_init_cmds(unet, key, on_host):
+ cmds = unet.topoconf.get(key, "")
+ cmds = cmds.replace("%CONFIGDIR%", str(unet.config_dirname))
+ cmds = cmds.replace("%RUNDIR%", str(unet.rundir))
+ cmds = cmds.strip()
+ if not cmds:
+ return
+
+ cmds += "\n"
+ c = commander if on_host else unet
+ o = c.cmd_raises(cmds)
+ self.logger.debug(
+ "run_init_cmds (on-host: %s): %s", on_host, cmd_error(0, o, "")
+ )
+
+ run_init_cmds(self, "initial-setup-host-cmd", True)
+ run_init_cmds(self, "initial-setup-cmd", False)
+
shellopt = self.cfgopt.getoption("--shell")
shellopt = shellopt if shellopt else ""
if shellopt == "all" or "." in shellopt.split(","):
@@ -3061,7 +3262,8 @@ done"""
if not rc:
continue
logging.info("Pulling missing image %s", image)
- aw = self.rootcmd.async_cmd_raises(f"podman pull {image}")
+
+ aw = self.rootcmd.async_cmd_raises_once(f"podman pull {image}")
tasks.append(asyncio.create_task(aw))
if not tasks:
return
diff --git a/tests/topotests/munet/testing/util.py b/tests/topotests/munet/testing/util.py
index 99687c0a83..02ff9bd69e 100644
--- a/tests/topotests/munet/testing/util.py
+++ b/tests/topotests/munet/testing/util.py
@@ -8,12 +8,17 @@
"""Utility functions useful when using munet testing functionailty in pytest."""
import asyncio
import datetime
+import fcntl
import functools
import logging
+import os
+import re
+import select
import sys
import time
from ..base import BaseMunet
+from ..base import Timeout
from ..cli import async_cli
@@ -23,6 +28,7 @@ from ..cli import async_cli
async def async_pause_test(desc=""):
+ """Pause the running of a test offering options for CLI or PDB."""
isatty = sys.stdout.isatty()
if not isatty:
desc = f" for {desc}" if desc else ""
@@ -49,11 +55,12 @@ async def async_pause_test(desc=""):
def pause_test(desc=""):
+ """Pause the running of a test offering options for CLI or PDB."""
asyncio.run(async_pause_test(desc))
def retry(retry_timeout, initial_wait=0, retry_sleep=2, expected=True):
- """decorator: retry while functions return is not None or raises an exception.
+ """Retry decorated function until it returns None, raises an exception, or timeout.
* `retry_timeout`: Retry for at least this many seconds; after waiting
initial_wait seconds
@@ -116,3 +123,91 @@ def retry(retry_timeout, initial_wait=0, retry_sleep=2, expected=True):
return func_retry
return _retry
+
+
+def readline(f, timeout=None):
+ """Read a line or timeout.
+
+ This function will take over the file object, the file object should not be used
+ outside of calling this function once you begin.
+
+ Return: A line, remaining buffer if EOF (subsequent calls will return ""), or None
+ for timeout.
+ """
+ fd = f.fileno()
+ if not hasattr(f, "munet_non_block_set"):
+ flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+ f.munet_non_block_set = True
+ f.munet_lines = []
+ f.munet_buf = ""
+
+ if f.munet_lines:
+ return f.munet_lines.pop(0)
+
+ timeout = Timeout(timeout)
+ remaining = timeout.remaining()
+ while remaining > 0:
+ ready, _, _ = select.select([fd], [], [], remaining)
+ if not ready:
+ return None
+
+ c = f.read()
+ if c is None:
+ logging.error("munet readline: unexpected None during read")
+ return None
+
+ if not c:
+ logging.debug("munet readline: got eof")
+ c = f.munet_buf
+ f.munet_buf = ""
+ return c
+
+ f.munet_buf += c
+ while "\n" in f.munet_buf:
+ a, f.munet_buf = f.munet_buf.split("\n", 1)
+ f.munet_lines.append(a + "\n")
+
+ if f.munet_lines:
+ return f.munet_lines.pop(0)
+
+ remaining = timeout.remaining()
+ return None
+
+
+def waitline(f, regex, timeout=120):
+ """Match a regex within lines from a file with a timeout.
+
+ This function will take over the file object (by calling `readline` above), the file
+ object should not be used outside of calling these functions once you begin.
+
+ Return: the match object or None.
+ """
+ timeo = Timeout(timeout)
+ while not timeo.is_expired():
+ line = readline(f, timeo.remaining())
+ if line is None:
+ break
+
+ if line == "":
+ logging.warning("waitline: got eof while matching '%s'", regex)
+ return None
+
+ assert line[-1] == "\n"
+ line = line[:-1]
+ if not line:
+ continue
+
+ logging.debug("waitline: searching: '%s' for '%s'", line, regex)
+ m = re.search(regex, line)
+ if m:
+ logging.debug("waitline: matched '%s'", m.group(0))
+ return m
+
+ logging.warning(
+ "Timeout while getting output matching '%s' within %ss (actual %ss)",
+ regex,
+ timeout,
+ timeo.elapsed(),
+ )
+ return None
diff --git a/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py b/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
index 1488e610c8..2a77c3b223 100644
--- a/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
+++ b/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
@@ -135,12 +135,10 @@ def test_pim_asm_igmp_join_acl():
expected = {
"r1-eth0":{
"name":"r1-eth0",
- "224.0.1.40":"*",
"229.1.1.1":None
},
"r1-eth2":{
"name":"r1-eth2",
- "224.0.1.40":"*",
"229.1.1.1":None
}
}
@@ -166,9 +164,7 @@ def test_pim_asm_igmp_join_acl():
"sources":[
{
"source":"*",
- "timer":"--:--",
"forwarded":False,
- "uptime":"*"
}
]
}
@@ -227,8 +223,6 @@ def test_pim_asm_igmp_join_acl():
"source":"*",
"group":"229.1.1.1",
"primaryAddr":"10.0.20.2",
- "sockFd":"*",
- "upTime":"*"
}
]
}
@@ -286,13 +280,11 @@ def test_pim_ssm_igmp_join_acl():
expected = {
"r1-eth0":{
"name":"r1-eth0",
- "224.0.1.40":"*",
"229.1.1.1":None,
"232.1.1.1":None
},
"r1-eth2":{
"name":"r1-eth2",
- "224.0.1.40":"*",
"229.1.1.1":None,
"232.1.1.1":None
}
@@ -319,9 +311,7 @@ def test_pim_ssm_igmp_join_acl():
"sources":[
{
"source":"10.0.20.2",
- "timer":"*",
"forwarded":False,
- "uptime":"*"
}
]
}
@@ -397,9 +387,7 @@ def test_pim_ssm_igmp_join_acl():
"sources":[
{
"source":"10.0.20.2",
- "timer":"*",
"forwarded":False,
- "uptime":"*"
}
]
}
@@ -422,8 +410,6 @@ def test_pim_ssm_igmp_join_acl():
"source":"10.0.20.2",
"group":"232.1.1.1",
"primaryAddr":"10.0.20.2",
- "sockFd":"*",
- "upTime":"*"
}
]
}
@@ -491,9 +477,7 @@ def test_pim_ssm_igmp_join_acl():
"sources":[
{
"source":"10.0.40.4",
- "timer":"*",
"forwarded":False,
- "uptime":"*"
}
]
}