From a2aad72a54ebc70f13fdb64b7578ced7f2af0f3b Mon Sep 17 00:00:00 2001 From: miatao Date: Mon, 13 Apr 2026 17:52:44 +0000 Subject: [PATCH 1/2] [show/vnet]: Fix ECMP scale formatting and per-endpoint MAC/VNI wrapping Signed-off-by: miatao --- show/vnet.py | 65 +++++++++++++----------- tests/mock_tables/appl_db.json | 5 ++ tests/mock_tables/state_db.json | 4 ++ tests/show_vnet_test.py | 87 ++++++++++++++++++++++++++------- 4 files changed, 114 insertions(+), 47 deletions(-) diff --git a/show/vnet.py b/show/vnet.py index 10c451b67..78157bdc3 100644 --- a/show/vnet.py +++ b/show/vnet.py @@ -413,8 +413,8 @@ def endpoint(args): have_status = False for k in vnet_rt_keys: val = appl_db.get_all(appl_db.APPL_DB, k) - endpoints = val.get('endpoint').split(',') - monitors = val.get('endpoint_monitor').split(',') + endpoints = val.get('endpoint').split(',') if val and 'endpoint' in val else [] + monitors = val.get('endpoint_monitor').split(',') if val and 'endpoint_monitor' in val else [] for idx, endpoint in enumerate(endpoints): if args == endpoint: prefix.append(k.split(":", 2)[2]) @@ -444,28 +444,42 @@ def routes(): def pretty_print(table, r, epval, mac_addr, vni, metric, state): endpoints = epval.split(',') - row_width = 3 - max_len = 0 - for ep in endpoints: - max_len = len(ep) if len(ep) > max_len else max_len - if max_len > 15: - row_width = 2 - iter = 0 - while iter < len(endpoints): - if iter +row_width > len(endpoints): - r.append(",".join(endpoints[iter:])) + # When mac_address or vni is a per-endpoint list, split so all three fields + # wrap in the same chunks, keeps rows aligned at any ECMP scale. + macs = mac_addr.split(',') if mac_addr and ',' in mac_addr else None + vnis = vni.split(',') if vni and ',' in vni else None + + # Derive row_width from the longest single item across all three fields so + # that long MAC addresses or IPv6 endpoints don't overflow the terminal. + all_items = list(endpoints) + if macs: + all_items.extend(macs) + if vnis: + all_items.extend(vnis) + max_len = max((len(item) for item in all_items), default=0) + row_width = 2 if max_len > 15 else 3 + + i = 0 + while i < len(endpoints): + r.append(",".join(endpoints[i:i + row_width])) + # Wrap mac and vni in sync with endpoints when they are lists + if macs: + r.append(",".join(macs[i:i + row_width]) if i < len(macs) else "") else: - r.append(",".join(endpoints[iter:iter + row_width])) - if iter == 0: - r.append(mac_addr) - r.append(vni) + r.append(mac_addr if i == 0 else "") + if vnis: + r.append(",".join(vnis[i:i + row_width]) if i < len(vnis) else "") + else: + r.append(vni if i == 0 else "") + if i == 0: r.append(metric) r.append(state) else: - r.extend(["", "", "", ""]) - iter += row_width + r.extend(["", ""]) + i += row_width table.append(r) - r = ["",""] + r = ["", ""] + @routes.command() @click.argument('vnet_name', required=False) @@ -544,16 +558,9 @@ def _show_tunnel_helper(vnet_name=None, appl_db=None, state_db=None): val = appl_db.get_all(appl_db.APPL_DB, k) val_state = state_db.get_all(state_db.STATE_DB, state_db_key) epval = val.get('endpoint') - if len(epval) < 40: - r.append(epval) - r.append(val.get('mac_address')) - r.append(val.get('vni')) - r.append(val.get('metric')) - if val_state: - r.append(val_state.get('state')) - table.append(r) - continue state = val_state.get('state') if val_state else "" - pretty_print(table, r, epval, val.get('mac_address'), val.get('vni'), val.get('metric'), state) + pretty_print(table, r, epval, + val.get('mac_address') or '', val.get('vni') or '', + val.get('metric') or '', state) click.echo(tabulate(table, tunnel_header)) diff --git a/tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json index 06a64ff9b..be9cb4ac4 100644 --- a/tests/mock_tables/appl_db.json +++ b/tests/mock_tables/appl_db.json @@ -404,6 +404,11 @@ "endpoint":"100.251.7.1", "endpoint_monitor":"100.251.7.1" }, + "VNET_ROUTE_TUNNEL_TABLE:Vnet_mac_vni_scale:10.0.0.0/24": { + "endpoint": "10.0.0.1,10.0.0.2,10.0.0.3,10.0.0.4,10.0.0.5,10.0.0.6", + "mac_address": "aa:bb:cc:00:00:01,aa:bb:cc:00:00:02,aa:bb:cc:00:00:03,aa:bb:cc:00:00:04,aa:bb:cc:00:00:05,aa:bb:cc:00:00:06", + "vni": "100,200,300,400,500,600" + }, "BGP_PROFILE_TABLE:FROM_SDN_SLB_ROUTES": { "community_id" : "1234:1235" }, diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index d88443fd8..6eca85da6 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -1620,6 +1620,10 @@ "active_endpoints":"fddd:a100:a251::a10:1,fddd:a101:a251::a10:1,fddd:a102:a251::a10:1,fddd:a103:a251::a10:1", "state":"active" }, + "VNET_ROUTE_TUNNEL_TABLE|Vnet_mac_vni_scale|10.0.0.0/24": { + "active_endpoints":"10.0.0.1,10.0.0.2,10.0.0.3,10.0.0.4,10.0.0.5,10.0.0.6", + "state":"active" + }, "BFD_SESSION_TABLE|default|default|100.251.7.1": { "state":"Up", "type": "async_active", diff --git a/tests/show_vnet_test.py b/tests/show_vnet_test.py index ae8e5782b..e7dc8a127 100644 --- a/tests/show_vnet_test.py +++ b/tests/show_vnet_test.py @@ -55,6 +55,36 @@ def test_Preety_print(self): ['Vnet_v6_in_v6-0', 'fddd:a156:a251::a6:1/128', '192.168.1.1', '', '', '0', 'active']] assert table == expected_output + # same endpoint, per-endpoint MACs and VNIs are wrapped in sync with endpoints + table = [] + row = ["TestVnet", "10.0.0.1/32"] + epval = "1.1.1.1,1.1.1.1,1.1.1.1,1.1.1.1" + mac_addr = "aa:bb:cc:00:00:01,aa:bb:cc:00:00:02,aa:bb:cc:00:00:03,aa:bb:cc:00:00:04" + vni = "100,200,300,400" + metric = "" + # MAC items are 17 chars > 15, so row_width=2 + vnet.pretty_print(table, row, epval, mac_addr, vni, metric, state) + expected_output = [ + ["TestVnet", "10.0.0.1/32", "1.1.1.1,1.1.1.1", "aa:bb:cc:00:00:01,aa:bb:cc:00:00:02", "100,200", "", "active"], + ["", "", "1.1.1.1,1.1.1.1", "aa:bb:cc:00:00:03,aa:bb:cc:00:00:04", "300,400", "", ""], + ] + assert table == expected_output + + # row_width decided by MAC item length, not just endpoint length + table = [] + row = ["TestVnet", "10.0.0.1/32"] + epval = "1.1.1.1,2.2.2.2,3.3.3.3" + mac_addr = "aa:bb:cc:00:00:01,aa:bb:cc:00:00:02,aa:bb:cc:00:00:03" + vni = "100,200,300" + metric = "5" + # All endpoints are <=7 chars, MAC items are 17 chars > 15 → row_width=2 + vnet.pretty_print(table, row, epval, mac_addr, vni, metric, state) + expected_output = [ + ["TestVnet", "10.0.0.1/32", "1.1.1.1,2.2.2.2", "aa:bb:cc:00:00:01,aa:bb:cc:00:00:02", "100,200", "5", "active"], + ["", "", "3.3.3.3", "aa:bb:cc:00:00:03", "300", "", ""], + ] + assert table == expected_output + def test_show_vnet_routes_all_basic(self): runner = CliRunner() db = Db() @@ -69,13 +99,16 @@ def test_show_vnet_routes_all_basic(self): test_v4_in_v4-0 160.164.191.1/32 100.102.4.1, 100.102.4.2, 100.102.4.3 Ethernet1, Ethernet2, Ethernet3 test_v4_in_v4-1 160.165.191.1/32 100.103.4.1, 100.103.4.2, 100.103.4.3 Ethernet1, Ethernet2, Ethernet3 -vnet name prefix endpoint mac address vni metric status ---------------- ------------------------ ------------------------------------------- ------------- ----- -------- -------- -Vnet_v6_in_v6-0 fddd:a156:a251::a6:1/128 fddd:a100:a251::a10:1,fddd:a101:a251::a10:1 active - fddd:a102:a251::a10:1,fddd:a103:a251::a10:1 -test_v4_in_v4-0 160.162.191.1/32 100.251.7.1 active -test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active -test_v4_in_v4-0 160.164.191.1/32 100.251.7.1 +vnet name prefix endpoint mac address vni metric status +------------------ ------------------------ ------------------------------------------- ----------------------------------- ------- -------- -------- +Vnet_mac_vni_scale 10.0.0.0/24 10.0.0.1,10.0.0.2 aa:bb:cc:00:00:01,aa:bb:cc:00:00:02 100,200 active + 10.0.0.3,10.0.0.4 aa:bb:cc:00:00:03,aa:bb:cc:00:00:04 300,400 + 10.0.0.5,10.0.0.6 aa:bb:cc:00:00:05,aa:bb:cc:00:00:06 500,600 +Vnet_v6_in_v6-0 fddd:a156:a251::a6:1/128 fddd:a100:a251::a10:1,fddd:a101:a251::a10:1 active + fddd:a102:a251::a10:1,fddd:a103:a251::a10:1 +test_v4_in_v4-0 160.162.191.1/32 100.251.7.1 active +test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active +test_v4_in_v4-0 160.164.191.1/32 100.251.7.1 """ assert result.output == expected_output @@ -93,10 +126,10 @@ def test_show_vnet_routes_all_vnetname(self): test_v4_in_v4-0 160.163.191.1/32 100.101.4.1, 100.101.4.2 Ethernet1, Ethernet2 test_v4_in_v4-0 160.164.191.1/32 100.102.4.1, 100.102.4.2, 100.102.4.3 Ethernet1, Ethernet2, Ethernet3 -vnet name prefix endpoint mac address vni metric status +vnet name prefix endpoint mac address vni metric status --------------- ---------------- ----------- ------------- ----- -------- -------- test_v4_in_v4-0 160.162.191.1/32 100.251.7.1 active -test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active +test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active test_v4_in_v4-0 160.164.191.1/32 100.251.7.1 """ assert result.output == expected_output @@ -108,13 +141,16 @@ def test_show_vnet_routes_tunnel_basic(self): result = runner.invoke(show.cli.commands['vnet'].commands['routes'].commands['tunnel'], [], obj=db) assert result.exit_code == 0 expected_output = """\ -vnet name prefix endpoint mac address vni metric status ---------------- ------------------------ ------------------------------------------- ------------- ----- -------- -------- -Vnet_v6_in_v6-0 fddd:a156:a251::a6:1/128 fddd:a100:a251::a10:1,fddd:a101:a251::a10:1 active - fddd:a102:a251::a10:1,fddd:a103:a251::a10:1 -test_v4_in_v4-0 160.162.191.1/32 100.251.7.1 active -test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active -test_v4_in_v4-0 160.164.191.1/32 100.251.7.1 +vnet name prefix endpoint mac address vni metric status +------------------ ------------------------ ------------------------------------------- ----------------------------------- ------- -------- -------- +Vnet_mac_vni_scale 10.0.0.0/24 10.0.0.1,10.0.0.2 aa:bb:cc:00:00:01,aa:bb:cc:00:00:02 100,200 active + 10.0.0.3,10.0.0.4 aa:bb:cc:00:00:03,aa:bb:cc:00:00:04 300,400 + 10.0.0.5,10.0.0.6 aa:bb:cc:00:00:05,aa:bb:cc:00:00:06 500,600 +Vnet_v6_in_v6-0 fddd:a156:a251::a6:1/128 fddd:a100:a251::a10:1,fddd:a101:a251::a10:1 active + fddd:a102:a251::a10:1,fddd:a103:a251::a10:1 +test_v4_in_v4-0 160.162.191.1/32 100.251.7.1 active +test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active +test_v4_in_v4-0 160.164.191.1/32 100.251.7.1 """ assert result.output == expected_output @@ -126,10 +162,10 @@ def test_show_vnet_routes_tunnel_vnetname(self): ['test_v4_in_v4-0'], obj=db) assert result.exit_code == 0 expected_output = """\ -vnet name prefix endpoint mac address vni metric status +vnet name prefix endpoint mac address vni metric status --------------- ---------------- ----------- ------------- ----- -------- -------- test_v4_in_v4-0 160.162.191.1/32 100.251.7.1 active -test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active +test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active test_v4_in_v4-0 160.164.191.1/32 100.251.7.1 """ assert result.output == expected_output @@ -163,6 +199,21 @@ def test_show_vnet_routes_local_vnetname(self): test_v4_in_v4-0 160.162.191.1/32 100.100.4.1 Ethernet1 test_v4_in_v4-0 160.163.191.1/32 100.101.4.1, 100.101.4.2 Ethernet1, Ethernet2 test_v4_in_v4-0 160.164.191.1/32 100.102.4.1, 100.102.4.2, 100.102.4.3 Ethernet1, Ethernet2, Ethernet3 +""" + assert result.output == expected_output + + def test_show_vnet_routes_tunnel_mac_vni_list(self): + runner = CliRunner() + db = Db() + result = runner.invoke(show.cli.commands['vnet'].commands['routes'].commands['tunnel'], + ['Vnet_mac_vni_scale'], obj=db) + assert result.exit_code == 0 + expected_output = """\ +vnet name prefix endpoint mac address vni metric status +------------------ ----------- ----------------- ----------------------------------- ------- -------- -------- +Vnet_mac_vni_scale 10.0.0.0/24 10.0.0.1,10.0.0.2 aa:bb:cc:00:00:01,aa:bb:cc:00:00:02 100,200 active + 10.0.0.3,10.0.0.4 aa:bb:cc:00:00:03,aa:bb:cc:00:00:04 300,400 + 10.0.0.5,10.0.0.6 aa:bb:cc:00:00:05,aa:bb:cc:00:00:06 500,600 """ assert result.output == expected_output From 04d4861b96315f93ddd0db38d75a34b0e65b9fbd Mon Sep 17 00:00:00 2001 From: miatao Date: Tue, 21 Apr 2026 18:42:40 +0000 Subject: [PATCH 2/2] [tests/vnet] Add scale unit tests for 511 and 2048 ecmp endpoints Signed-off-by: miatao --- doc/Command-Reference.md | 31 ++++-- show/vnet.py | 4 +- tests/mock_tables/appl_db.json | 6 ++ tests/mock_tables/state_db.json | 4 + tests/mock_tables/vnet_ecmp/appl_db.json | 14 +++ tests/mock_tables/vnet_ecmp/state_db.json | 10 ++ tests/show_vnet_test.py | 126 +++++++++++++++++++--- 7 files changed, 172 insertions(+), 23 deletions(-) create mode 100644 tests/mock_tables/vnet_ecmp/appl_db.json create mode 100644 tests/mock_tables/vnet_ecmp/state_db.json diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 573e7763b..ecf2eb434 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -12371,6 +12371,8 @@ This command displays vnet neighbor information about all the vnets configured i This command displays either all routes information about all the vnets or a specified vnet configured in the device. It also shows the vnet routes which are configured but may or may not be active based on endpoint BFD status. +For ECMP tunnel routes with per-endpoint `mac_address` or `vni` lists, the endpoints, MAC addresses, and VNIs are wrapped together in aligned chunks (2 per row when any item exceeds 15 characters, 3 per row otherwise). + - Usage: ``` @@ -12386,11 +12388,12 @@ This command displays either all routes information about all the vnets or a spe Vnet_2000 100.100.3.0/24 Ethernet52 Vnet_3000 100.100.4.0/24 Vlan2000 - vnet name prefix endpoint mac address vni metric status - ----------- -------------- ---------- ----------------- ----- -------- -------- - Vnet_2000 100.100.1.1/32 10.10.10.1 0 active - Vnet_3000 100.100.2.1/32 10.10.10.2 00:00:00:00:03:04 inactive - Vnet_3000 100.100.2.3/32 10.10.10.6 00:00:00:00:03:04 + vnet name prefix endpoint mac address vni metric status + ---------------- --------------- ----------------------------------- ----------------------------------- ------- -------- -------- + Vnet_2000 100.100.1.1/32 10.10.10.1,10.10.10.2 aa:bb:cc:00:00:01,aa:bb:cc:00:00:02 100,200 active + 10.10.10.3,10.10.10.4 aa:bb:cc:00:00:03,aa:bb:cc:00:00:04 300,400 + Vnet_3000 100.100.2.1/32 10.10.10.5 00:00:00:00:03:04 0 inactive + Vnet_3000 100.100.2.3/32 10.10.10.6 00:00:00:00:03:04 ``` **show vnet routes local []** @@ -12417,6 +12420,8 @@ This command displays either local routes information about all the vnets or a s This command displays tunnel routes information about all the vnets or a specified vnet configured in the device. +For ECMP routes with per-endpoint `mac_address` or `vni` lists, the endpoints, MAC addresses, and VNIs are wrapped together in aligned chunks. An optional vnet name argument filters the output to a single vnet. + - Usage: ``` @@ -12427,11 +12432,17 @@ This command displays tunnel routes information about all the vnets or a specifi ``` admin@sonic:~$ show vnet routes tunnel - vnet name prefix endpoint mac address vni metric status - ----------- -------------- ---------- ----------------- ----- -------- -------- - Vnet_2000 100.100.1.1/32 10.10.10.1 0 active - Vnet_3000 100.100.2.1/32 10.10.10.2 00:00:00:00:03:04 inactive - Vnet_3000 100.100.2.3/32 10.10.10.6 00:00:00:00:03:04 + vnet name prefix endpoint mac address vni metric status + ---------------- --------------- ----------------------------------- ----------------------------------- ------- -------- -------- + Vnet_2000 100.100.1.1/32 10.10.10.1,10.10.10.2 aa:bb:cc:00:00:01,aa:bb:cc:00:00:02 100,200 active + 10.10.10.3,10.10.10.4 aa:bb:cc:00:00:03,aa:bb:cc:00:00:04 300,400 + Vnet_3000 100.100.2.1/32 10.10.10.5 00:00:00:00:03:04 0 inactive + + admin@sonic:~$ show vnet routes tunnel Vnet_2000 + vnet name prefix endpoint mac address vni metric status + ---------------- --------------- ----------------------------------- ----------------------------------- ------- -------- -------- + Vnet_2000 100.100.1.1/32 10.10.10.1,10.10.10.2 aa:bb:cc:00:00:01,aa:bb:cc:00:00:02 100,200 active + 10.10.10.3,10.10.10.4 aa:bb:cc:00:00:03,aa:bb:cc:00:00:04 300,400 ``` #### Vnet config commands diff --git a/show/vnet.py b/show/vnet.py index 78157bdc3..db87ee324 100644 --- a/show/vnet.py +++ b/show/vnet.py @@ -559,8 +559,10 @@ def _show_tunnel_helper(vnet_name=None, appl_db=None, state_db=None): val_state = state_db.get_all(state_db.STATE_DB, state_db_key) epval = val.get('endpoint') state = val_state.get('state') if val_state else "" + raw_metric = val.get('metric') + metric = int(raw_metric) if raw_metric else '' pretty_print(table, r, epval, val.get('mac_address') or '', val.get('vni') or '', - val.get('metric') or '', state) + metric, state) click.echo(tabulate(table, tunnel_header)) diff --git a/tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json index be9cb4ac4..b0741a02d 100644 --- a/tests/mock_tables/appl_db.json +++ b/tests/mock_tables/appl_db.json @@ -409,6 +409,12 @@ "mac_address": "aa:bb:cc:00:00:01,aa:bb:cc:00:00:02,aa:bb:cc:00:00:03,aa:bb:cc:00:00:04,aa:bb:cc:00:00:05,aa:bb:cc:00:00:06", "vni": "100,200,300,400,500,600" }, + "VNET_ROUTE_TUNNEL_TABLE:Vnet_7127926:30.0.21.0/24": { + "endpoint": "100.106.230.44,10.134.85.10,100.106.229.38,100.106.229.170,100.106.228.160,10.134.84.24,100.106.230.168,10.90.92.16,10.224.116.42,100.106.228.134,100.106.229.171,100.106.228.161", + "mac_address": "00:22:48:03:8c:f8,60:45:bd:a3:8d:ab,60:45:bd:a3:21:88,60:45:bd:a2:e4:39,7c:1e:52:06:89:0f,7c:1e:52:06:8b:cd,60:45:bd:a3:8f:ae,60:45:bd:a2:e8:f9,60:45:bd:a2:e5:ee,60:45:bd:a4:be:3e,60:45:bd:a3:8d:ac,7c:1e:52:06:89:10", + "vni": "7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926", + "metric": "5" + }, "BGP_PROFILE_TABLE:FROM_SDN_SLB_ROUTES": { "community_id" : "1234:1235" }, diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index 6eca85da6..62cf897dd 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -1624,6 +1624,10 @@ "active_endpoints":"10.0.0.1,10.0.0.2,10.0.0.3,10.0.0.4,10.0.0.5,10.0.0.6", "state":"active" }, + "VNET_ROUTE_TUNNEL_TABLE|Vnet_7127926|30.0.21.0/24": { + "active_endpoints":"100.106.230.44,10.134.85.10,100.106.229.38,100.106.229.170,100.106.228.160,10.134.84.24,100.106.230.168,10.90.92.16,10.224.116.42,100.106.228.134,100.106.229.171,100.106.228.161", + "state":"active" + }, "BFD_SESSION_TABLE|default|default|100.251.7.1": { "state":"Up", "type": "async_active", diff --git a/tests/mock_tables/vnet_ecmp/appl_db.json b/tests/mock_tables/vnet_ecmp/appl_db.json new file mode 100644 index 000000000..2e805244b --- /dev/null +++ b/tests/mock_tables/vnet_ecmp/appl_db.json @@ -0,0 +1,14 @@ +{ + "VNET_ROUTE_TUNNEL_TABLE:Vnet_7127926:30.0.21.0/24": { + "endpoint": "100.106.230.44,10.134.85.10,100.106.229.38,100.106.229.170,100.106.228.160,10.134.84.24,100.106.230.168,10.90.92.16,10.224.116.42,100.106.228.134,100.106.229.171,100.106.228.161", + "mac_address": "00:22:48:03:8c:f8,60:45:bd:a3:8d:ab,60:45:bd:a3:21:88,60:45:bd:a2:e4:39,7c:1e:52:06:89:0f,7c:1e:52:06:8b:cd,60:45:bd:a3:8f:ae,60:45:bd:a2:e8:f9,60:45:bd:a2:e5:ee,60:45:bd:a4:be:3e,60:45:bd:a3:8d:ac,7c:1e:52:06:89:10", + "vni": "7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926", + "metric": "5" + }, + "VNET_ROUTE_TUNNEL_TABLE:Vnet_7127926:30.0.20.0/24": { + "endpoint": "100.106.230.44,10.134.85.10,100.106.229.38,100.106.229.170,100.106.228.160,10.134.84.24,100.106.230.168,10.90.92.16,10.224.116.42,100.106.228.134", + "mac_address": "00:22:48:03:8c:f8,60:45:bd:a3:8d:ab,60:45:bd:a3:21:88,60:45:bd:a2:e4:39,7c:1e:52:06:89:0f,7c:1e:52:06:8b:cd,60:45:bd:a3:8f:ae,60:45:bd:a2:e8:f9,60:45:bd:a2:e5:ee,60:45:bd:a4:be:3e", + "vni": "7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926,7127926", + "metric": "5" + } +} diff --git a/tests/mock_tables/vnet_ecmp/state_db.json b/tests/mock_tables/vnet_ecmp/state_db.json new file mode 100644 index 000000000..a79d39830 --- /dev/null +++ b/tests/mock_tables/vnet_ecmp/state_db.json @@ -0,0 +1,10 @@ +{ + "VNET_ROUTE_TUNNEL_TABLE|Vnet_7127926|30.0.21.0/24": { + "active_endpoints": "100.106.230.44,10.134.85.10,100.106.229.38,100.106.229.170,100.106.228.160,10.134.84.24,100.106.230.168,10.90.92.16,10.224.116.42,100.106.228.134,100.106.229.171,100.106.228.161", + "state": "active" + }, + "VNET_ROUTE_TUNNEL_TABLE|Vnet_7127926|30.0.20.0/24": { + "active_endpoints": "100.106.230.44,10.134.85.10,100.106.229.38,100.106.229.170,100.106.228.160,10.134.84.24,100.106.230.168,10.90.92.16,10.224.116.42,100.106.228.134", + "state": "active" + } +} diff --git a/tests/show_vnet_test.py b/tests/show_vnet_test.py index e7dc8a127..75bd0f154 100644 --- a/tests/show_vnet_test.py +++ b/tests/show_vnet_test.py @@ -1,9 +1,11 @@ # flake8: noqa: E501 import os +import pytest from click.testing import CliRunner from utilities_common.db import Db import show.main as show import show.vnet as vnet +from tests.mock_tables import dbconnector class TestShowVnetRoutesAll(object): @classmethod @@ -85,6 +87,49 @@ def test_Preety_print(self): ] assert table == expected_output + @pytest.mark.parametrize("N,vnet_name,prefix,metric", [ + (511, "Vnet_scale_511", "10.0.0.0/9", "5"), # current production scale (odd → last row is a singleton) + (2048, "Vnet_scale_2048", "10.0.0.0/8", "10"), # target maximum scale (even → all rows are pairs) + ]) + def test_pretty_print_scale(self, N, vnet_name, prefix, metric): + """Scale test for pretty_print at 511 (current production) and 2048 (target max) endpoints. + + Endpoints are generated as 10.{i>>8}.{i&0xff}.1, MACs as aa:bb:cc:00:{i>>8}:{i&0xff}, + and VNIs as sequential integers — all unique, no DB required, runs in well under 1 ms. + """ + endpoints_list = [f"10.{(i >> 8) & 0xff}.{i & 0xff}.1" for i in range(N)] + macs_list = [f"aa:bb:cc:{(i >> 16) & 0xff:02x}:{(i >> 8) & 0xff:02x}:{i & 0xff:02x}" for i in range(N)] + vnis_list = [str(i + 1) for i in range(N)] + + table = [] + row = [vnet_name, prefix] + vnet.pretty_print(table, row, + ",".join(endpoints_list), + ",".join(macs_list), + ",".join(vnis_list), + metric, "active") + + expected_rows = (N + 1) // 2 # ceil(N/2) — works for both odd and even + assert len(table) == expected_rows + + # First row carries vnet name, prefix, metric, and state + assert table[0][0] == vnet_name + assert table[0][1] == prefix + assert table[0][2] == f"{endpoints_list[0]},{endpoints_list[1]}" + assert table[0][3] == f"{macs_list[0]},{macs_list[1]}" + assert table[0][4] == f"{vnis_list[0]},{vnis_list[1]}" + assert table[0][5] == metric + assert table[0][6] == "active" + + # All subsequent rows have empty name, prefix, metric, and state + for r in table[1:]: + assert r[0] == "" and r[1] == "" and r[5] == "" and r[6] == "" + + # Round-trip: no data loss and endpoints/MACs/VNIs stay aligned across all rows + assert ",".join(r[2] for r in table) == ",".join(endpoints_list) + assert ",".join(r[3] for r in table) == ",".join(macs_list) + assert ",".join(r[4] for r in table) == ",".join(vnis_list) + def test_show_vnet_routes_all_basic(self): runner = CliRunner() db = Db() @@ -99,15 +144,21 @@ def test_show_vnet_routes_all_basic(self): test_v4_in_v4-0 160.164.191.1/32 100.102.4.1, 100.102.4.2, 100.102.4.3 Ethernet1, Ethernet2, Ethernet3 test_v4_in_v4-1 160.165.191.1/32 100.103.4.1, 100.103.4.2, 100.103.4.3 Ethernet1, Ethernet2, Ethernet3 -vnet name prefix endpoint mac address vni metric status ------------------- ------------------------ ------------------------------------------- ----------------------------------- ------- -------- -------- -Vnet_mac_vni_scale 10.0.0.0/24 10.0.0.1,10.0.0.2 aa:bb:cc:00:00:01,aa:bb:cc:00:00:02 100,200 active +vnet name prefix endpoint mac address vni metric status +------------------ ------------------------ ------------------------------------------- ----------------------------------- --------------- -------- -------- +Vnet_7127926 30.0.21.0/24 100.106.230.44,10.134.85.10 00:22:48:03:8c:f8,60:45:bd:a3:8d:ab 7127926,7127926 5 active + 100.106.229.38,100.106.229.170 60:45:bd:a3:21:88,60:45:bd:a2:e4:39 7127926,7127926 + 100.106.228.160,10.134.84.24 7c:1e:52:06:89:0f,7c:1e:52:06:8b:cd 7127926,7127926 + 100.106.230.168,10.90.92.16 60:45:bd:a3:8f:ae,60:45:bd:a2:e8:f9 7127926,7127926 + 10.224.116.42,100.106.228.134 60:45:bd:a2:e5:ee,60:45:bd:a4:be:3e 7127926,7127926 + 100.106.229.171,100.106.228.161 60:45:bd:a3:8d:ac,7c:1e:52:06:89:10 7127926,7127926 +Vnet_mac_vni_scale 10.0.0.0/24 10.0.0.1,10.0.0.2 aa:bb:cc:00:00:01,aa:bb:cc:00:00:02 100,200 active 10.0.0.3,10.0.0.4 aa:bb:cc:00:00:03,aa:bb:cc:00:00:04 300,400 10.0.0.5,10.0.0.6 aa:bb:cc:00:00:05,aa:bb:cc:00:00:06 500,600 -Vnet_v6_in_v6-0 fddd:a156:a251::a6:1/128 fddd:a100:a251::a10:1,fddd:a101:a251::a10:1 active +Vnet_v6_in_v6-0 fddd:a156:a251::a6:1/128 fddd:a100:a251::a10:1,fddd:a101:a251::a10:1 active fddd:a102:a251::a10:1,fddd:a103:a251::a10:1 -test_v4_in_v4-0 160.162.191.1/32 100.251.7.1 active -test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active +test_v4_in_v4-0 160.162.191.1/32 100.251.7.1 active +test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active test_v4_in_v4-0 160.164.191.1/32 100.251.7.1 """ assert result.output == expected_output @@ -141,15 +192,21 @@ def test_show_vnet_routes_tunnel_basic(self): result = runner.invoke(show.cli.commands['vnet'].commands['routes'].commands['tunnel'], [], obj=db) assert result.exit_code == 0 expected_output = """\ -vnet name prefix endpoint mac address vni metric status ------------------- ------------------------ ------------------------------------------- ----------------------------------- ------- -------- -------- -Vnet_mac_vni_scale 10.0.0.0/24 10.0.0.1,10.0.0.2 aa:bb:cc:00:00:01,aa:bb:cc:00:00:02 100,200 active +vnet name prefix endpoint mac address vni metric status +------------------ ------------------------ ------------------------------------------- ----------------------------------- --------------- -------- -------- +Vnet_7127926 30.0.21.0/24 100.106.230.44,10.134.85.10 00:22:48:03:8c:f8,60:45:bd:a3:8d:ab 7127926,7127926 5 active + 100.106.229.38,100.106.229.170 60:45:bd:a3:21:88,60:45:bd:a2:e4:39 7127926,7127926 + 100.106.228.160,10.134.84.24 7c:1e:52:06:89:0f,7c:1e:52:06:8b:cd 7127926,7127926 + 100.106.230.168,10.90.92.16 60:45:bd:a3:8f:ae,60:45:bd:a2:e8:f9 7127926,7127926 + 10.224.116.42,100.106.228.134 60:45:bd:a2:e5:ee,60:45:bd:a4:be:3e 7127926,7127926 + 100.106.229.171,100.106.228.161 60:45:bd:a3:8d:ac,7c:1e:52:06:89:10 7127926,7127926 +Vnet_mac_vni_scale 10.0.0.0/24 10.0.0.1,10.0.0.2 aa:bb:cc:00:00:01,aa:bb:cc:00:00:02 100,200 active 10.0.0.3,10.0.0.4 aa:bb:cc:00:00:03,aa:bb:cc:00:00:04 300,400 10.0.0.5,10.0.0.6 aa:bb:cc:00:00:05,aa:bb:cc:00:00:06 500,600 -Vnet_v6_in_v6-0 fddd:a156:a251::a6:1/128 fddd:a100:a251::a10:1,fddd:a101:a251::a10:1 active +Vnet_v6_in_v6-0 fddd:a156:a251::a6:1/128 fddd:a100:a251::a10:1,fddd:a101:a251::a10:1 active fddd:a102:a251::a10:1,fddd:a103:a251::a10:1 -test_v4_in_v4-0 160.162.191.1/32 100.251.7.1 active -test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active +test_v4_in_v4-0 160.162.191.1/32 100.251.7.1 active +test_v4_in_v4-0 160.163.191.1/32 100.251.7.1 0 active test_v4_in_v4-0 160.164.191.1/32 100.251.7.1 """ assert result.output == expected_output @@ -217,6 +274,51 @@ def test_show_vnet_routes_tunnel_mac_vni_list(self): """ assert result.output == expected_output + +class TestShowVnetRoutesECMP(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["UTILITIES_UNIT_TESTING"] = "1" + dbconnector.topo = "vnet_ecmp" + + @classmethod + def teardown_class(cls): + dbconnector.topo = None + + def test_show_vnet_routes_tunnel_real_world_ecmp(self): + """Real-world ECMP dataset: 287 entries (10 unique ep/MAC pairs repeated) for Vnet_7127926. + + 30.0.20.0/24 uses the full duplicated dataset as it appears in production APPL_DB. + 30.0.21.0/24 is the 12-ep scale variant with all unique endpoints — exact output pinned. + """ + runner = CliRunner() + db = Db() + result = runner.invoke(show.cli.commands['vnet'].commands['routes'].commands['tunnel'], + ['Vnet_7127926'], obj=db) + assert result.exit_code == 0 + output = result.output + + # 30.0.21.0/24: 12 unique endpoints, 2-per-row (12 > 2 → row_width=2). Exact output pinned. + expected_21 = ( + "Vnet_7127926 30.0.21.0/24 100.106.230.44,10.134.85.10 00:22:48:03:8c:f8,60:45:bd:a3:8d:ab 7127926,7127926 5 active\n" + " 100.106.229.38,100.106.229.170 60:45:bd:a3:21:88,60:45:bd:a2:e4:39 7127926,7127926\n" + " 100.106.228.160,10.134.84.24 7c:1e:52:06:89:0f,7c:1e:52:06:8b:cd 7127926,7127926\n" + " 100.106.230.168,10.90.92.16 60:45:bd:a3:8f:ae,60:45:bd:a2:e8:f9 7127926,7127926\n" + " 10.224.116.42,100.106.228.134 60:45:bd:a2:e5:ee,60:45:bd:a4:be:3e 7127926,7127926\n" + " 100.106.229.171,100.106.228.161 60:45:bd:a3:8d:ac,7c:1e:52:06:89:10 7127926,7127926\n" + ) + assert expected_21 in output + + # 30.0.20.0/24: 287 entries. Content assertions only. + assert "30.0.20.0/24" in output + for ep in ["100.106.230.44", "10.134.85.10", "100.106.229.38", "100.106.229.170", + "100.106.228.160", "10.134.84.24", "100.106.230.168", "10.90.92.16", + "10.224.116.42", "100.106.228.134"]: + assert ep in output, f"endpoint {ep} missing from output" + assert output.count("\n") > 10 + + class TestShowVnetAdvertisedRoutesIPX(object): @classmethod def setup_class(cls):