Skip to content

Commit 3dabc06

Browse files
authored
Merge pull request #5 from gechiang/202205
[Manual Sync] From sonic-net/sonic-utilities 202205 to Azure/sonic-utitities.msft repo
2 parents c72ddaf + 673564e commit 3dabc06

File tree

10 files changed

+228
-37
lines changed

10 files changed

+228
-37
lines changed

config/vlan.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,24 @@ def del_vlan(db, vid, no_restart_dhcp_relay):
112112
# We need to restart dhcp_relay service after dhcpv6_relay config change
113113
if is_dhcp_relay_running():
114114
dhcp_relay_util.handle_restart_dhcp_relay_service()
115+
116+
vlans = db.cfgdb.get_keys('VLAN')
117+
if not vlans:
118+
docker_exec_cmd = "docker exec -i swss {}"
119+
_, rc = clicommon.run_command(docker_exec_cmd.format("supervisorctl status ndppd"), ignore_error=True, return_cmd=True)
120+
if rc == 0:
121+
click.echo("No VLANs remaining, stopping ndppd service")
122+
clicommon.run_command(docker_exec_cmd.format("supervisorctl stop ndppd"), ignore_error=True, return_cmd=True)
123+
clicommon.run_command(docker_exec_cmd.format("rm -f /etc/supervisor/conf.d/ndppd.conf"), ignore_error=True, return_cmd=True)
124+
clicommon.run_command(docker_exec_cmd.format("supervisorctl update"), return_cmd=True)
115125

116126

117127
def restart_ndppd():
118128
verify_swss_running_cmd = "docker container inspect -f '{{.State.Status}}' swss"
119129
docker_exec_cmd = "docker exec -i swss {}"
130+
ndppd_status_cmd= "supervisorctl status ndppd"
131+
ndppd_conf_copy_cmd = "cp /usr/share/sonic/templates/ndppd.conf /etc/supervisor/conf.d/"
132+
supervisor_update_cmd = "supervisorctl update"
120133
ndppd_config_gen_cmd = "sonic-cfggen -d -t /usr/share/sonic/templates/ndppd.conf.j2,/etc/ndppd.conf"
121134
ndppd_restart_cmd = "supervisorctl restart ndppd"
122135

@@ -126,9 +139,16 @@ def restart_ndppd():
126139
click.echo(click.style('SWSS container is not running, changes will take effect the next time the SWSS container starts', fg='red'),)
127140
return
128141

129-
clicommon.run_command(docker_exec_cmd.format(ndppd_config_gen_cmd), display_cmd=True)
142+
_, rc = clicommon.run_command(docker_exec_cmd.format(ndppd_status_cmd), ignore_error=True, return_cmd=True)
143+
144+
if rc != 0:
145+
clicommon.run_command(docker_exec_cmd.format(ndppd_conf_copy_cmd))
146+
clicommon.run_command(docker_exec_cmd.format(supervisor_update_cmd), return_cmd=True)
147+
148+
click.echo("Starting ndppd service")
149+
clicommon.run_command(docker_exec_cmd.format(ndppd_config_gen_cmd))
130150
sleep(3)
131-
clicommon.run_command(docker_exec_cmd.format(ndppd_restart_cmd), display_cmd=True)
151+
clicommon.run_command(docker_exec_cmd.format(ndppd_restart_cmd), return_cmd=True)
132152

133153

134154
@vlan.command('proxy_arp')

generic_config_updater/field_operation_validators.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,8 @@
33

44
def rdma_config_update_validator():
55
version_info = device_info.get_sonic_version_info()
6-
build_version = version_info.get('build_version')
76
asic_type = version_info.get('asic_type')
87

98
if (asic_type != 'mellanox' and asic_type != 'broadcom' and asic_type != 'cisco-8000'):
109
return False
11-
12-
version_substrings = build_version.split('.')
13-
branch_version = None
14-
15-
for substring in version_substrings:
16-
if substring.isdigit() and re.match(r'^\d{8}$', substring):
17-
branch_version = substring
18-
break
19-
20-
if branch_version is None:
21-
return False
22-
23-
if asic_type == 'cisco-8000':
24-
return branch_version >= "20201200"
25-
else:
26-
return branch_version >= "20181100"
10+
return True

scripts/fast-reboot

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ STRICT=no
1717
REBOOT_METHOD="/sbin/kexec -e"
1818
ASSISTANT_IP_LIST=""
1919
ASSISTANT_SCRIPT="/usr/local/bin/neighbor_advertiser"
20+
LAG_KEEPALIVE_SCRIPT="/usr/local/bin/lag_keepalive.py"
2021
WATCHDOG_UTIL="/usr/local/bin/watchdogutil"
2122
DEVPATH="/usr/share/sonic/device"
2223
PLATFORM=$(sonic-cfggen -H -v DEVICE_METADATA.localhost.platform)
@@ -645,6 +646,13 @@ fi
645646
# disable trap-handlers which were set before
646647
trap '' EXIT HUP INT QUIT TERM KILL ABRT ALRM
647648
649+
# start sending LACPDUs to keep the LAGs refreshed
650+
# this is a non-blocking call, and the process will die in 300s
651+
debug "Starting lag_keepalive to send LACPDUs ..."
652+
timeout 300 python ${LAG_KEEPALIVE_SCRIPT} &
653+
# give the lag_keepalive script a chance to get ready (30s) and collect one lacpdu before going down (30s)
654+
sleep 60
655+
648656
if [ -x ${LOG_SSD_HEALTH} ]; then
649657
debug "Collecting logs to check ssd health before ${REBOOT_TYPE}..."
650658
${LOG_SSD_HEALTH}

scripts/lag_keepalive.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python3
2+
3+
from scapy.config import conf
4+
conf.ipv6_enabled = False
5+
from scapy.all import sendp, sniff
6+
from swsscommon.swsscommon import ConfigDBConnector
7+
import time, threading, traceback
8+
import syslog
9+
10+
SYSLOG_ID = 'lag_keepalive'
11+
12+
13+
def log_info(msg):
14+
syslog.openlog(SYSLOG_ID)
15+
syslog.syslog(syslog.LOG_INFO, msg)
16+
syslog.closelog()
17+
18+
19+
def log_error(msg):
20+
syslog.openlog(SYSLOG_ID)
21+
syslog.syslog(syslog.LOG_ERR, msg)
22+
syslog.closelog()
23+
24+
25+
def sniff_lacpdu(device_mac, lag_member, lag_member_to_packet):
26+
sniffed_packet = sniff(iface=lag_member,
27+
filter="ether proto 0x8809 and ether src {}".format(device_mac),
28+
count=1, timeout=30)
29+
lag_member_to_packet[lag_member] = sniffed_packet
30+
31+
32+
def get_lacpdu_per_lag_member():
33+
appDB = ConfigDBConnector()
34+
appDB.db_connect('APPL_DB')
35+
appDB_lag_info = appDB.get_keys('LAG_MEMBER_TABLE')
36+
configDB = ConfigDBConnector()
37+
configDB.db_connect('CONFIG_DB')
38+
device_mac = configDB.get(configDB.CONFIG_DB, "DEVICE_METADATA|localhost", "mac")
39+
hwsku = configDB.get(configDB.CONFIG_DB, "DEVICE_METADATA|localhost", "hwsku")
40+
active_lag_members = list()
41+
lag_member_to_packet = dict()
42+
sniffer_threads = list()
43+
for lag_entry in appDB_lag_info:
44+
lag_name = str(lag_entry[0])
45+
oper_status = appDB.get(appDB.APPL_DB,"LAG_TABLE:{}".format(lag_name), "oper_status")
46+
if oper_status == "up":
47+
# only apply the workaround for active lags
48+
lag_member = str(lag_entry[1])
49+
active_lag_members.append(lag_member)
50+
# use threading to capture lacpdus from several lag members simultaneously
51+
sniffer_thread = threading.Thread(target=sniff_lacpdu,
52+
args=(device_mac, lag_member, lag_member_to_packet))
53+
sniffer_thread.start()
54+
sniffer_threads.append(sniffer_thread)
55+
56+
# sniff for lacpdu should finish in <= 30s. sniff timeout is also set to 30s
57+
for sniffer in sniffer_threads:
58+
sniffer.join(timeout=30)
59+
60+
return active_lag_members, lag_member_to_packet
61+
62+
63+
def lag_keepalive(lag_member_to_packet):
64+
while True:
65+
for lag_member, packet in lag_member_to_packet.items():
66+
try:
67+
sendp(packet, iface=lag_member, verbose=False)
68+
except Exception:
69+
# log failure and continue to send lacpdu
70+
traceback_msg = traceback.format_exc()
71+
log_error("Failed to send LACPDU packet from interface {} with error: {}".format(
72+
lag_member, traceback_msg))
73+
continue
74+
log_info("sent LACPDU packets via {}".format(lag_member_to_packet.keys()))
75+
time.sleep(1)
76+
77+
78+
def main():
79+
while True:
80+
try:
81+
active_lag_members, lag_member_to_packet = get_lacpdu_per_lag_member()
82+
if len(active_lag_members) != len(lag_member_to_packet.keys()):
83+
log_error("Failed to capture LACPDU packets for some lag members. " +\
84+
"Active lag members: {}. LACPDUs captured for: {}".format(
85+
active_lag_members, lag_member_to_packet.keys()))
86+
87+
log_info("ready to send LACPDU packets via {}".format(lag_member_to_packet.keys()))
88+
except Exception:
89+
traceback_msg = traceback.format_exc()
90+
log_error("Failed to get LAG members and LACPDUs with error: {}".format(
91+
traceback_msg))
92+
# keep attempting until sniffed packets are ready
93+
continue
94+
# if no exceptions are thrown, break from loop as LACPDUs are ready to be sent
95+
break
96+
97+
if lag_member_to_packet:
98+
# start an infinite loop to keep sending lacpdus from lag member ports
99+
lag_keepalive(lag_member_to_packet)
100+
101+
if __name__ == "__main__":
102+
main()

scripts/route_check.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ def filter_out_vnet_routes(routes):
434434
vnet_routes = []
435435

436436
for vnet_route_db_key in vnet_routes_db_keys:
437-
vnet_route_attrs = vnet_route_db_key.split(':')
437+
vnet_route_attrs = vnet_route_db_key.split(':', 1)
438438
vnet_name = vnet_route_attrs[0]
439439
vnet_route = vnet_route_attrs[1]
440440
vnet_routes.append(vnet_route)

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
'scripts/intfutil',
114114
'scripts/intfstat',
115115
'scripts/ipintutil',
116+
'scripts/lag_keepalive.py',
116117
'scripts/lldpshow',
117118
'scripts/log_ssd_health',
118119
'scripts/mellanox_buffer_migrator.py',

sfputil/main.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,18 @@ def reset(port_name):
12011201

12021202
i += 1
12031203

1204+
def update_firmware_info_to_state_db(port_name):
1205+
physical_port = logical_port_to_physical_port_index(port_name)
1206+
1207+
namespaces = multi_asic.get_front_end_namespaces()
1208+
for namespace in namespaces:
1209+
state_db = SonicV2Connector(use_unix_socket_path=False, namespace=namespace)
1210+
if state_db is not None:
1211+
state_db.connect(state_db.STATE_DB)
1212+
active_firmware, inactive_firmware = platform_chassis.get_sfp(physical_port).get_transceiver_info_firmware_versions()
1213+
state_db.set(state_db.STATE_DB, 'TRANSCEIVER_INFO|{}'.format(port_name), "active_firmware", active_firmware)
1214+
state_db.set(state_db.STATE_DB, 'TRANSCEIVER_INFO|{}'.format(port_name), "inactive_firmware", inactive_firmware)
1215+
12041216
# 'firmware' subgroup
12051217
@cli.group()
12061218
def firmware():
@@ -1271,7 +1283,7 @@ def is_fw_switch_done(port_name):
12711283

12721284
if fw_info['status'] == True:
12731285
(ImageA, ImageARunning, ImageACommitted, ImageAInvalid,
1274-
ImageB, ImageBRunning, ImageBCommitted, ImageBInvalid) = fw_info['result']
1286+
ImageB, ImageBRunning, ImageBCommitted, ImageBInvalid, _, _) = fw_info['result']
12751287

12761288
if (ImageARunning == 1) and (ImageAInvalid == 1): # ImageA is running, but also invalid.
12771289
click.echo("FW info error : ImageA shows running, but also shows invalid!")
@@ -1382,6 +1394,7 @@ def download_firmware(port_name, filepath):
13821394
sfp.set_optoe_write_max(1)
13831395

13841396
status = api.cdb_firmware_download_complete()
1397+
update_firmware_info_to_state_db(port_name)
13851398
click.echo('CDB: firmware download complete')
13861399
return status
13871400

@@ -1409,6 +1422,7 @@ def run(port_name, mode):
14091422
click.echo('Failed to run firmware in mode={}! CDB status: {}'.format(mode, status))
14101423
sys.exit(EXIT_FAIL)
14111424

1425+
update_firmware_info_to_state_db(port_name)
14121426
click.echo("Firmware run in mode={} success".format(mode))
14131427

14141428
# 'commit' subcommand
@@ -1430,6 +1444,7 @@ def commit(port_name):
14301444
click.echo('Failed to commit firmware! CDB status: {}'.format(status))
14311445
sys.exit(EXIT_FAIL)
14321446

1447+
update_firmware_info_to_state_db(port_name)
14331448
click.echo("Firmware commit successful")
14341449

14351450
# 'upgrade' subcommand

sonic_installer/bootloader/uboot.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ def set_fips(self, image, enable):
8989
cmdline = out.strip()
9090
cmdline = re.sub('^linuxargs=', '', cmdline)
9191
cmdline = re.sub(r' sonic_fips=[^\s]', '', cmdline) + " sonic_fips=" + fips
92-
run_command('/usr/bin/fw_setenv linuxargs ' + cmdline)
92+
cmdline = '"' + cmdline + '"'
93+
run_command('/usr/bin/fw_setenv linuxargs ' + cmdline )
9394
click.echo('Done')
9495

9596
def get_fips(self, image):

tests/sfputil_test.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -710,12 +710,12 @@ def test_run_firmwre(self, mock_chassis):
710710
@patch('sfputil.main.logical_port_to_physical_port_index', MagicMock(return_value=1))
711711
@pytest.mark.parametrize("mock_response, expected", [
712712
({'status': False, 'result': None} , -1),
713-
({'status': True, 'result': ("1.0.1", 1, 1, 0, "1.0.2", 0, 0, 0)} , -1),
714-
({'status': True, 'result': ("1.0.1", 0, 0, 0, "1.0.2", 1, 1, 0)} , -1),
715-
({'status': True, 'result': ("1.0.1", 1, 0, 0, "1.0.2", 0, 1, 0)} , 1),
716-
({'status': True, 'result': ("1.0.1", 0, 1, 0, "1.0.2", 1, 0, 0)} , 1),
717-
({'status': True, 'result': ("1.0.1", 1, 0, 1, "1.0.2", 0, 1, 0)} , -1),
718-
({'status': True, 'result': ("1.0.1", 0, 1, 0, "1.0.2", 1, 0, 1)} , -1),
713+
({'status': True, 'result': ("1.0.1", 1, 1, 0, "1.0.2", 0, 0, 0, "1.0.1", "1.0.2")} , -1),
714+
({'status': True, 'result': ("1.0.1", 0, 0, 0, "1.0.2", 1, 1, 0, "1.0.2", "1.0.1")} , -1),
715+
({'status': True, 'result': ("1.0.1", 1, 0, 0, "1.0.2", 0, 1, 0, "1.0.1", "1.0.2")} , 1),
716+
({'status': True, 'result': ("1.0.1", 0, 1, 0, "1.0.2", 1, 0, 0, "1.0.2", "1.0.1")} , 1),
717+
({'status': True, 'result': ("1.0.1", 1, 0, 1, "1.0.2", 0, 1, 0, "1.0.1", "1.0.2")} , -1),
718+
({'status': True, 'result': ("1.0.1", 0, 1, 0, "1.0.2", 1, 0, 1, "1.0.2", "1.0.1")} , -1),
719719
720720
# "is_fw_switch_done" function will waiting until timeout under below condition, so that this test will spend around 1min.
721721
({'status': False, 'result': 0} , -1),
@@ -790,3 +790,32 @@ def test_firmware_download_RJ45(self):
790790
result = runner.invoke(sfputil.cli.commands['firmware'].commands['download'], ["Ethernet0", "a.b"])
791791
assert result.output == 'This functionality is not applicable for RJ45 port Ethernet0.\n'
792792
assert result.exit_code == EXIT_FAIL
793+
794+
@patch('sfputil.main.is_sfp_present', MagicMock(return_value=True))
795+
@patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=False))
796+
@patch('sfputil.main.run_firmware', MagicMock(return_value=1))
797+
@patch('sfputil.main.update_firmware_info_to_state_db', MagicMock())
798+
def test_firmware_run_cli(self):
799+
runner = CliRunner()
800+
result = runner.invoke(sfputil.cli.commands['firmware'].commands['run'], ["Ethernet0"])
801+
assert result.exit_code == 0
802+
803+
@patch('sfputil.main.is_sfp_present', MagicMock(return_value=True))
804+
@patch('sfputil.main.is_port_type_rj45', MagicMock(return_value=False))
805+
@patch('sfputil.main.commit_firmware', MagicMock(return_value=1))
806+
@patch('sfputil.main.update_firmware_info_to_state_db', MagicMock())
807+
def test_firmware_commit_cli(self):
808+
runner = CliRunner()
809+
result = runner.invoke(sfputil.cli.commands['firmware'].commands['commit'], ["Ethernet0"])
810+
assert result.exit_code == 0
811+
812+
@patch('sfputil.main.logical_port_to_physical_port_index', MagicMock(return_value=1))
813+
@patch('sonic_py_common.multi_asic.get_front_end_namespaces', MagicMock(return_value=['']))
814+
@patch('sfputil.main.SonicV2Connector', MagicMock())
815+
@patch('sfputil.main.platform_chassis')
816+
def test_update_firmware_info_to_state_db(self, mock_chassis):
817+
mock_sfp = MagicMock()
818+
mock_chassis.get_sfp = MagicMock(return_value=mock_sfp)
819+
mock_sfp.get_transceiver_info_firmware_versions.return_value = ['a.b.c', 'd.e.f']
820+
821+
sfputil.update_firmware_info_to_state_db("Ethernet0")

tests/vlan_test.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,27 @@ def test_config_vlan_del_vlan(self, mock_restart_dhcp_relay_service):
403403
assert result.exit_code == 0
404404
assert result.output == show_vlan_brief_empty_output
405405

406+
def test_config_vlan_del_last_vlan(self):
407+
runner = CliRunner()
408+
db = Db()
409+
db.cfgdb.delete_table("VLAN_MEMBER")
410+
db.cfgdb.delete_table("VLAN_INTERFACE")
411+
db.cfgdb.set_entry("VLAN", "Vlan2000", None)
412+
db.cfgdb.set_entry("VLAN", "Vlan3000", None)
413+
db.cfgdb.set_entry("VLAN", "Vlan4000", None)
414+
415+
with mock.patch("utilities_common.cli.run_command", mock.Mock(return_value=("", 0))) as mock_run_command:
416+
result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db)
417+
print(result.exit_code)
418+
print(result.output)
419+
mock_run_command.assert_has_calls([
420+
mock.call("docker exec -i swss supervisorctl status ndppd", ignore_error=True, return_cmd=True),
421+
mock.call("docker exec -i swss supervisorctl stop ndppd", ignore_error=True, return_cmd=True),
422+
mock.call("docker exec -i swss rm -f /etc/supervisor/conf.d/ndppd.conf", ignore_error=True, return_cmd=True),
423+
mock.call("docker exec -i swss supervisorctl update", return_cmd=True)
424+
])
425+
assert result.exit_code == 0
426+
406427
def test_config_vlan_del_nonexist_vlan_member(self):
407428
runner = CliRunner()
408429

@@ -533,19 +554,29 @@ def test_config_vlan_proxy_arp_with_nonexist_vlan_intf(self):
533554
assert result.exit_code != 0
534555
assert "Interface Vlan1001 does not exist" in result.output
535556

536-
def test_config_vlan_proxy_arp_enable(self, mock_restart_dhcp_relay_service):
537-
runner = CliRunner()
538-
db = Db()
557+
def test_config_vlan_proxy_arp_enable(self):
558+
mock_cli_returns = [("running", 0),("", 1)] + [("", 0)] * 4
559+
with mock.patch("utilities_common.cli.run_command", mock.Mock(side_effect=mock_cli_returns)) as mock_run_command:
560+
runner = CliRunner()
561+
db = Db()
539562

540-
result = runner.invoke(config.config.commands["vlan"].commands["proxy_arp"], ["1000", "enabled"], obj=db)
563+
result = runner.invoke(config.config.commands["vlan"].commands["proxy_arp"], ["1000", "enabled"], obj=db)
541564

542-
print(result.exit_code)
543-
print(result.output)
565+
print(result.exit_code)
566+
print(result.output)
567+
568+
expected_calls = [mock.call("docker container inspect -f '{{.State.Status}}' swss", return_cmd=True),
569+
mock.call('docker exec -i swss supervisorctl status ndppd', ignore_error=True, return_cmd=True),
570+
mock.call('docker exec -i swss cp /usr/share/sonic/templates/ndppd.conf /etc/supervisor/conf.d/'),
571+
mock.call('docker exec -i swss supervisorctl update', return_cmd=True),
572+
mock.call('docker exec -i swss sonic-cfggen -d -t /usr/share/sonic/templates/ndppd.conf.j2,/etc/ndppd.conf'),
573+
mock.call('docker exec -i swss supervisorctl restart ndppd', return_cmd=True)]
574+
mock_run_command.assert_has_calls(expected_calls)
544575

545-
assert result.exit_code == 0
546-
assert db.cfgdb.get_entry("VLAN_INTERFACE", "Vlan1000") == {"proxy_arp": "enabled"}
576+
assert result.exit_code == 0
577+
assert db.cfgdb.get_entry("VLAN_INTERFACE", "Vlan1000") == {"proxy_arp": "enabled"}
547578

548-
def test_config_vlan_proxy_arp_disable(self, mock_restart_dhcp_relay_service):
579+
def test_config_vlan_proxy_arp_disable(self):
549580
runner = CliRunner()
550581
db = Db()
551582

0 commit comments

Comments
 (0)