Skip to content

Commit ad0a3d3

Browse files
authored
Fetch capability of mirror before configuring it (#4089)
What I did Fetch capability of ingress/egress mirror before configuring it and avoid configuring ingress/egress mirror on a platform that does not support it. How I did it Check the capability in PORT_INGRESS_MIRROR_CAPABLE and PORT_EGRESS_MIRROR_CAPABLE in STATE_DB table SWITCH_CAPABILITY. The capability of ingress/egress mirror is inserted to STATE_DB by orchagent during initialization. How to verify it Manual test and unit test
1 parent 73f5a1a commit ad0a3d3

File tree

2 files changed

+153
-4
lines changed

2 files changed

+153
-4
lines changed

config/main.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,32 @@ def interface_has_mirror_config(ctx, mirror_table, dst_port, src_port, direction
11401140

11411141
return False
11421142

1143+
1144+
def is_port_mirror_capability_supported(direction, namespace=None):
1145+
""" Check if port mirror capability is supported for the given direction """
1146+
state_db = SonicV2Connector(use_unix_socket_path=True, namespace=namespace)
1147+
state_db.connect(state_db.STATE_DB, False)
1148+
entry_name = "SWITCH_CAPABILITY|switch"
1149+
1150+
# If no direction is specified, check both ingress and egress capabilities
1151+
if not direction:
1152+
ingress_supported = state_db.get(state_db.STATE_DB, entry_name, "PORT_INGRESS_MIRROR_CAPABLE")
1153+
egress_supported = state_db.get(state_db.STATE_DB, entry_name, "PORT_EGRESS_MIRROR_CAPABLE")
1154+
return ingress_supported == "true" and egress_supported == "true"
1155+
1156+
if direction in ['rx', 'both']:
1157+
ingress_supported = state_db.get(state_db.STATE_DB, entry_name, "PORT_INGRESS_MIRROR_CAPABLE")
1158+
if ingress_supported != "true":
1159+
return False
1160+
1161+
if direction in ['tx', 'both']:
1162+
egress_supported = state_db.get(state_db.STATE_DB, entry_name, "PORT_EGRESS_MIRROR_CAPABLE")
1163+
if egress_supported != "true":
1164+
return False
1165+
1166+
return True
1167+
1168+
11431169
def validate_mirror_session_config(config_db, session_name, dst_port, src_port, direction):
11441170
""" Check if SPAN mirror-session config is valid """
11451171
ctx = click.get_current_context()
@@ -1161,7 +1187,6 @@ def validate_mirror_session_config(config_db, session_name, dst_port, src_port,
11611187
if interface_is_in_vlan(vlan_member_table, dst_port):
11621188
ctx.fail("Error: Destination Interface {} has vlan config".format(dst_port))
11631189

1164-
11651190
if interface_is_in_portchannel(portchannel_member_table, dst_port):
11661191
ctx.fail("Error: Destination Interface {} has portchannel config".format(dst_port))
11671192

@@ -1182,6 +1207,12 @@ def validate_mirror_session_config(config_db, session_name, dst_port, src_port,
11821207
if direction not in ['rx', 'tx', 'both']:
11831208
ctx.fail("Error: Direction {} is invalid".format(direction))
11841209

1210+
# Check port mirror capability before allowing configuration
1211+
# If direction is provided, check the specific direction
1212+
if not is_port_mirror_capability_supported(direction):
1213+
ctx.fail("Error: Port mirror direction '{}' is not supported by the ASIC".format(
1214+
direction if direction else 'both'))
1215+
11851216
return True
11861217

11871218
def cli_sroute_to_config(ctx, command_str, strict_nh = True):

tests/config_mirror_session_test.py

Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def test_mirror_session_add():
8989
["test_session", "100.1.1.1", "2.2.2.2", "8", "63", "0", "0"])
9090

9191
mocked.assert_called_with("test_session", "100.1.1.1", "2.2.2.2", 8, 63, 0, 0, None)
92-
92+
9393
result = runner.invoke(
9494
config.config.commands["mirror_session"].commands["add"],
9595
["test_session", "100.1.1.1", "2.2.2.2", "8", "63"])
@@ -141,7 +141,7 @@ def test_mirror_session_erspan_add():
141141
["test_session", "1.1.1.1", "2.2.2.2", "6", "63", "65536", "100"])
142142
assert result.exit_code != 0
143143
assert ERR_MSG_GRE_TYPE_FAILURE in result.stdout
144-
144+
145145
result = runner.invoke(
146146
config.config.commands["mirror_session"].commands["erspan"].commands["add"],
147147
["test_session", "1.1.1.1", "2.2.2.2", "6", "63", "abcd", "100"])
@@ -295,7 +295,7 @@ def test_mirror_session_span_add():
295295
result = runner.invoke(
296296
config.config.commands["mirror_session"].commands["span"].commands["add"],
297297
["test_session", "Ethernet8", "Ethernet4", "tx", "100"])
298-
298+
299299
mocked.assert_called_with("test_session", "Ethernet8", "Ethernet4", "tx", 100, None)
300300

301301
result = runner.invoke(
@@ -355,3 +355,121 @@ def test_mirror_session_remove_invalid_yang_validation():
355355
["mrr_sample"])
356356
print(result.output)
357357
assert "Invalid ConfigDB. Error" in result.output
358+
359+
360+
def test_mirror_session_capability_checking():
361+
"""Test mirror session capability checking functionality"""
362+
config.ADHOC_VALIDATION = True
363+
runner = CliRunner()
364+
365+
# Test 1: Check that capability checking fails when direction is not supported
366+
with mock.patch('config.main.is_port_mirror_capability_supported') as mock_capability:
367+
mock_capability.return_value = False
368+
369+
# Test with rx direction - should fail
370+
result = runner.invoke(
371+
config.config.commands["mirror_session"].commands["span"].commands["add"],
372+
["test_session", "Ethernet20", "Ethernet24", "rx", "100"])
373+
374+
assert result.exit_code != 0
375+
assert "Error: Port mirror direction 'rx' is not supported by the ASIC" in result.output
376+
377+
# Test with tx direction - should fail
378+
result = runner.invoke(
379+
config.config.commands["mirror_session"].commands["span"].commands["add"],
380+
["test_session", "Ethernet20", "Ethernet24", "tx", "100"])
381+
382+
assert result.exit_code != 0
383+
assert "Error: Port mirror direction 'tx' is not supported by the ASIC" in result.output
384+
385+
# Test with both direction - should fail
386+
result = runner.invoke(
387+
config.config.commands["mirror_session"].commands["span"].commands["add"],
388+
["test_session", "Ethernet20", "Ethernet24", "both", "100"])
389+
390+
assert result.exit_code != 0
391+
assert "Error: Port mirror direction 'both' is not supported by the ASIC" in result.output
392+
393+
394+
def test_mirror_session_capability_function():
395+
"""Test the is_port_mirror_capability_supported function directly"""
396+
397+
# Test 1: Test with valid STATE_DB responses
398+
with mock.patch('config.main.SonicV2Connector') as mock_connector:
399+
mock_instance = mock.Mock()
400+
mock_connector.return_value = mock_instance
401+
402+
# Mock successful connection
403+
mock_instance.connect.return_value = None
404+
405+
# Test ingress capability check
406+
mock_instance.get.side_effect = lambda db, entry, field: {
407+
("SWITCH_CAPABILITY|switch", "PORT_INGRESS_MIRROR_CAPABLE"): "true",
408+
("SWITCH_CAPABILITY|switch", "PORT_EGRESS_MIRROR_CAPABLE"): "true"
409+
}.get((entry, field), "false")
410+
411+
# Test rx direction
412+
result = config.is_port_mirror_capability_supported("rx")
413+
assert result is True
414+
415+
# Test tx direction
416+
result = config.is_port_mirror_capability_supported("tx")
417+
assert result is True
418+
419+
# Test both direction
420+
result = config.is_port_mirror_capability_supported("both")
421+
assert result is True
422+
423+
# Test no direction (should check both)
424+
result = config.is_port_mirror_capability_supported(None)
425+
assert result is True
426+
427+
# Test 2: Test with partial capability support
428+
with mock.patch('config.main.SonicV2Connector') as mock_connector:
429+
mock_instance = mock.Mock()
430+
mock_connector.return_value = mock_instance
431+
432+
# Mock successful connection
433+
mock_instance.connect.return_value = None
434+
435+
# Mock only ingress supported
436+
mock_instance.get.side_effect = lambda db, entry, field: {
437+
("SWITCH_CAPABILITY|switch", "PORT_INGRESS_MIRROR_CAPABLE"): "true",
438+
("SWITCH_CAPABILITY|switch", "PORT_EGRESS_MIRROR_CAPABLE"): "false"
439+
}.get((entry, field), "false")
440+
441+
# Test rx direction (should pass)
442+
result = config.is_port_mirror_capability_supported("rx")
443+
assert result is True
444+
445+
# Test tx direction (should fail)
446+
result = config.is_port_mirror_capability_supported("tx")
447+
assert result is False
448+
449+
# Test both direction (should fail)
450+
result = config.is_port_mirror_capability_supported("both")
451+
assert result is False
452+
453+
# Test no direction (should fail)
454+
result = config.is_port_mirror_capability_supported(None)
455+
assert result is False
456+
457+
# Test 3: Test with no capability support
458+
with mock.patch('config.main.SonicV2Connector') as mock_connector:
459+
mock_instance = mock.Mock()
460+
mock_connector.return_value = mock_instance
461+
462+
# Mock successful connection
463+
mock_instance.connect.return_value = None
464+
465+
# Mock no capabilities supported
466+
mock_instance.get.side_effect = lambda db, entry, field: {
467+
("SWITCH_CAPABILITY|switch", "PORT_INGRESS_MIRROR_CAPABLE"): "false",
468+
("SWITCH_CAPABILITY|switch", "PORT_EGRESS_MIRROR_CAPABLE"): "false"
469+
}.get((entry, field), "false")
470+
471+
# All directions should fail
472+
assert config.is_port_mirror_capability_supported("rx") is False
473+
assert config.is_port_mirror_capability_supported("tx") is False
474+
assert config.is_port_mirror_capability_supported("both") is False
475+
assert config.is_port_mirror_capability_supported(None) is False

0 commit comments

Comments
 (0)