From 9e43ffa26a290003b646a5599a7c501fd51c9e1b Mon Sep 17 00:00:00 2001 From: Rajendra Dendukuri Date: Mon, 26 Sep 2022 07:14:21 -0700 Subject: [PATCH 1/3] [ZTP] Improvements to the connectivity-check plugin - The user can now specify arguments that can be passed on to ping command when performing the connectivity check. The "args" field in the configuration section of the ztp.json needs to be specified. - The user can choose to restart dhcp if connectivity check fails in its first attempt. This is useful when the switch has rebooted and all the in-band interfaces are not yet active. Use "dhcp" : true in the configuration section of the ztp.json. - Added unit test cases --- src/usr/lib/ztp/plugins/connectivity-check | 39 +++++- tests/test_connectivity-check.py | 143 +++++++++++++++++++++ 2 files changed, 176 insertions(+), 6 deletions(-) diff --git a/src/usr/lib/ztp/plugins/connectivity-check b/src/usr/lib/ztp/plugins/connectivity-check index b8d0360..0caadac 100755 --- a/src/usr/lib/ztp/plugins/connectivity-check +++ b/src/usr/lib/ztp/plugins/connectivity-check @@ -39,7 +39,7 @@ class ConnectivityCheck: ''' self.__input_file = input_file - def pingHosts(self, host_list, retry_count, retry_interval, ping_count, deadline, timeout, ipv6=False): + def pingHosts(self, host_list, interface, retry_count, retry_interval, ping_count, deadline, timeout, args, ipv6=False, dhcp=False): # Prepare list of hosts to ping if not isinstance(host_list, list): @@ -61,14 +61,20 @@ class ConnectivityCheck: iter_list = list(_host_list) # Loop through current host list for host in iter_list: + if interface is not None and not isString(interface): + interface = None if isString(host): pingCmd = "ping -q -c " + str(ping_count) + " " if deadline is not None: pingCmd += "-w " + str(deadline) + " " if timeout is not None: pingCmd += "-W " + str(timeout) + " " + if interface is not None: + pingCmd += "-I " + interface + " " if ipv6: - pingCmd = pingCmd + " -6 " + pingCmd = pingCmd + " -6 " + if args is not None: + pingCmd = pingCmd + " " + args + " " logger.info('connectivity-check: Pinging host \'%s\'.' % (host)) updateActivity('connectivity-check: Pinging host \'%s\'.' % (host)) # Ping the host @@ -76,15 +82,27 @@ class ConnectivityCheck: if rv == 0: # Host is alive, remove it from the list _host_list.remove(host) - logger.info('connectivity-check: Host \'%s\' not reachable.' % (host)) - updateActivity('connectivity-check: Host \'%s\' not reachable.' % (host)) + else: + logger.info('connectivity-check: Host \'%s\' not reachable.' % (host)) + updateActivity('connectivity-check: Host \'%s\' not reachable.' % (host)) else: # Discard invalid hosts _host_list.remove(host) if len(_host_list) == 0: break + + if dhcp: + logger.info('Restarting networking to establish connectivity') + updateActivity('Restarting networking to establish connectivity') + runCommand('systemctl restart interfaces-config') + logger.info('Restarted networking') + updateActivity('Restarted networking') + + logger.info('connectivity-check: Sleeping for %d seconds before retrying.' % (retry_interval)) + updateActivity('Sleeping for %d seconds before retrying' % (retry_interval)) time.sleep(retry_interval) + if retry_count != -1: retry_count = retry_count - 1 return rc @@ -108,6 +126,9 @@ class ConnectivityCheck: logger.error('connectivity-check: Host list not provided.') sys.exit(1) + # Interface name + interface = getField(section_data, 'interface', str, None) + # Time interval in seconds to wait before retrying ping to hosts retry_interval = getField(section_data, 'retry-interval', int, 5) if retry_interval < 0: @@ -127,10 +148,16 @@ class ConnectivityCheck: # Time to wait for a response, in seconds timeout = getField(section_data, 'timeout', int, None) + # Time to wait for a response, in seconds + retry_dhcp = getField(section_data, 'retry-dhcp', bool, default_value=False) + + # Additional ping arguments + args = getField(section_data, 'args', str, None) + # Ping ipv4 host list if section_data.get('ping-hosts') is not None: logger.info('connectivity-check: Attempting to connect to IPv4 hosts %s.' % (section_data.get('ping-hosts'))) - if self.pingHosts(section_data.get('ping-hosts'), retry_count, retry_interval, ping_count, deadline, timeout) is False: + if self.pingHosts(section_data.get('ping-hosts'), interface, retry_count, retry_interval, ping_count, deadline, timeout, args, dhcp=retry_dhcp) is False: logger.error('connectivity-check: IPv4 hosts not reachable.') sys.exit(1) logger.info('connectivity-check: All IPv4 hosts %s reachable' %(section_data.get('ping-hosts'))) @@ -138,7 +165,7 @@ class ConnectivityCheck: # Ping ipv6 host list if section_data.get('ping6-hosts') is not None: logger.info('connectivity-check: Attempting to connect to IPv6 hosts %s.' % (section_data.get('ping6-hosts'))) - if self.pingHosts(section_data.get('ping6-hosts'), retry_count, retry_interval, ping_count, deadline, timeout, ipv6=True) is False: + if self.pingHosts(section_data.get('ping6-hosts'), interface, retry_count, retry_interval, ping_count, deadline, timeout, args, ipv6=True, dhcp=retry_dhcp) is False: logger.error('connectivity-check: IPv6 hosts not reachable.') sys.exit(1) logger.info('connectivity-check: All IPv6 hosts %s reachable' %(section_data.get('ping6-hosts'))) diff --git a/tests/test_connectivity-check.py b/tests/test_connectivity-check.py index e7b95b8..88bab8f 100644 --- a/tests/test_connectivity-check.py +++ b/tests/test_connectivity-check.py @@ -87,6 +87,98 @@ def test_ping_localhost(self, tmpdir): assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 0 + def test_ping_localhost_interface_nok(self, tmpdir): + '''! + Test case pinging IPV4 localhost using an interface: + Verify that pinging IPV4 localhost fails on non existent interface + ''' + d = tmpdir.mkdir("valid") + fh = d.join("input.json") + fh.write(""" + { + "connectivity-check": { + "ping-hosts": "127.0.0.1", + "interface": "ethX", + "retry-count": 2, + "retry-interval": 15, + "timeout": "10" + } + } + """) + connectivity_check = ConnectivityCheck(str(fh)) + with pytest.raises(SystemExit) as pytest_wrapped_e: + connectivity_check.main() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 1 + + def test_ping_localhost_interface_ok(self, tmpdir): + '''! + Test case pinging IPV4 localhost using an interface: + Verify that pinging IPV4 localhost succeeds on existent interface + ''' + d = tmpdir.mkdir("valid") + fh = d.join("input.json") + fh.write(""" + { + "connectivity-check": { + "ping-hosts": "127.0.0.1", + "interface": "lo", + "deadline": 15 + } + } + """) + connectivity_check = ConnectivityCheck(str(fh)) + with pytest.raises(SystemExit) as pytest_wrapped_e: + connectivity_check.main() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 + + def test_ping_localhost_args_nok(self, tmpdir): + '''! + Test case pinging IPV4 localhost using arguments: + Verify that pinging IPV4 localhost fails on non existent interface + ''' + d = tmpdir.mkdir("valid") + fh = d.join("input.json") + fh.write(""" + { + "connectivity-check": { + "ping-hosts": "127.0.0.1", + "args": "-I ethX", + "retry-count": 2, + "retry-interval": 15, + "timeout": "10" + } + } + """) + connectivity_check = ConnectivityCheck(str(fh)) + with pytest.raises(SystemExit) as pytest_wrapped_e: + connectivity_check.main() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 1 + + def test_ping_localhost_args_ok(self, tmpdir): + '''! + Test case pinging IPV4 localhost using arguments: + Verify that pinging IPV4 localhost succeeds on existent interface + ''' + d = tmpdir.mkdir("valid") + fh = d.join("input.json") + fh.write(""" + { + "connectivity-check": { + "ping-hosts": "127.0.0.1", + "args": "-I lo", + "deadline": 15 + } + } + """) + connectivity_check = ConnectivityCheck(str(fh)) + with pytest.raises(SystemExit) as pytest_wrapped_e: + connectivity_check.main() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 + def test_ping_non_routable_address(self, tmpdir): '''! Test case pinging non routable IPV4 address: @@ -98,17 +190,21 @@ def test_ping_non_routable_address(self, tmpdir): { "01-connectivity-check": { "retry-count": 2, + "retry-dhcp" : true, "retry-interval": 15, "timeout": "10", "ping-hosts": ["192.0.2.1", 123] } } """) + (rc, interfaces_exit_time, cmd_stderr) = runCommand("systemctl show --value -p ExecMainExitTimestampMonotonic interfaces-config") connectivity_check = ConnectivityCheck(str(fh)) with pytest.raises(SystemExit) as pytest_wrapped_e: connectivity_check.main() assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 1 + (rc, interfaces_exit_time_new, cmd_stderr) = runCommand("systemctl show --value -p ExecMainExitTimestampMonotonic interfaces-config") + assert (int(interfaces_exit_time_new[0]) > int(interfaces_exit_time[0])) def test_ping_ipv6_localhost(self, tmpdir): '''! @@ -132,6 +228,53 @@ def test_ping_ipv6_localhost(self, tmpdir): assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == 0 + def test_ping_ipv6_localhost_interface_nok(self, tmpdir): + '''! + Test case pinging IPV6 localhost using an interface + Verify that pinging IPV6 localhost fails + ''' + d = tmpdir.mkdir("valid") + fh = d.join("input.json") + fh.write(""" + { + "connectivity-check": { + "ping6-hosts": ["0:0:0:0:0:0:0:1"], + "interface": "ethX", + "retry-count": 2, + "retry-interval": 15, + "timeout": "10" + } + } + """) + connectivity_check = ConnectivityCheck(str(fh)) + with pytest.raises(SystemExit) as pytest_wrapped_e: + connectivity_check.main() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 1 + + def test_ping_ipv6_localhost_interface_ok(self, tmpdir): + '''! + Test case pinging IPV6 localhost using an interface + Verify that pinging IPV6 localhost succeeds + ''' + d = tmpdir.mkdir("valid") + fh = d.join("input.json") + fh.write(""" + { + "connectivity-check": { + "ping6-hosts": ["0:0:0:0:0:0:0:1"], + "interface": "lo", + "retry-count": -2, + "retry-interval": -15 + } + } + """) + connectivity_check = ConnectivityCheck(str(fh)) + with pytest.raises(SystemExit) as pytest_wrapped_e: + connectivity_check.main() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 + def test_ping_ipv6_non_routable_address(self, tmpdir): '''! Test case pinging non routable IPV6 address: From 38dbd2eaa3430bf2934de6a17336f2f9b1427d8f Mon Sep 17 00:00:00 2001 From: Rajendra Dendukuri Date: Fri, 30 Sep 2022 11:48:56 -0700 Subject: [PATCH 2/3] Removed a redundant error check in connectivity-check plugin --- src/usr/lib/ztp/plugins/connectivity-check | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/usr/lib/ztp/plugins/connectivity-check b/src/usr/lib/ztp/plugins/connectivity-check index 0caadac..00b296a 100755 --- a/src/usr/lib/ztp/plugins/connectivity-check +++ b/src/usr/lib/ztp/plugins/connectivity-check @@ -61,8 +61,6 @@ class ConnectivityCheck: iter_list = list(_host_list) # Loop through current host list for host in iter_list: - if interface is not None and not isString(interface): - interface = None if isString(host): pingCmd = "ping -q -c " + str(ping_count) + " " if deadline is not None: From 6f0eff7eb29c22e3bdfb3845de1e1728e6f79b7e Mon Sep 17 00:00:00 2001 From: Rajendra Dendukuri Date: Thu, 6 Oct 2022 11:51:40 -0700 Subject: [PATCH 3/3] Modified log message when restarting the networking service --- src/usr/lib/ztp/plugins/connectivity-check | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/usr/lib/ztp/plugins/connectivity-check b/src/usr/lib/ztp/plugins/connectivity-check index 00b296a..b31414e 100755 --- a/src/usr/lib/ztp/plugins/connectivity-check +++ b/src/usr/lib/ztp/plugins/connectivity-check @@ -91,8 +91,8 @@ class ConnectivityCheck: break if dhcp: - logger.info('Restarting networking to establish connectivity') - updateActivity('Restarting networking to establish connectivity') + logger.info('Restarting networking service to restart DHCP') + updateActivity('Restarting networking service to restart DHCP') runCommand('systemctl restart interfaces-config') logger.info('Restarted networking') updateActivity('Restarted networking')