From 5b593c6196a3579f0f6d21fa03772923dab2d7ce 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 13b44855371a831013a3b6fe12e1f715e828cb4a 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 2fdc1f28a471945be4ff355a5056bd9c8ab1db91 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')