Skip to content

Commit

Permalink
[ZTP] Improvements to the connectivity-check plugin
Browse files Browse the repository at this point in the history
 - 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
  • Loading branch information
rajendra-dendukuri committed Sep 26, 2022
1 parent f7dd3c5 commit 45a0a82
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 6 deletions.
39 changes: 33 additions & 6 deletions src/usr/lib/ztp/plugins/connectivity-check
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -61,30 +61,48 @@ 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
rv = runCommand(pingCmd + host, False)
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
Expand All @@ -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:
Expand All @@ -127,18 +148,24 @@ 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')))

# 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')))
Expand Down
143 changes: 143 additions & 0 deletions tests/test_connectivity-check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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):
'''!
Expand All @@ -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:
Expand Down

0 comments on commit 45a0a82

Please sign in to comment.