Update ping wrapper
- Allow to execute ping from inside network namespaces - Refactor multi-ip ping api Change-Id: I5d48ba39de2a4c94e6579b2a07017807b6a6e132
This commit is contained in:
parent
98b75fe60b
commit
49f37c6fe5
@ -23,9 +23,8 @@ from tobiko.shell.ping import _ping
|
||||
from tobiko.shell.ping import _statistics
|
||||
|
||||
|
||||
assert_reachable_ips = _assert.assert_reachable_ips
|
||||
get_reachable_ips = _assert.get_reachable_ips
|
||||
get_unreachable_ips = _assert.get_unreachable_ips
|
||||
assert_reachable_hosts = _assert.assert_reachable_hosts
|
||||
assert_unreachable_hosts = _assert.assert_unreachable_hosts
|
||||
|
||||
PingException = _exception.PingException
|
||||
PingError = _exception.PingError
|
||||
@ -42,11 +41,15 @@ ping_parameters = _parameters.ping_parameters
|
||||
get_ping_parameters = _parameters.get_ping_parameters
|
||||
default_ping_parameters = _parameters.default_ping_parameters
|
||||
|
||||
list_reachable_hosts = _ping.list_reachable_hosts
|
||||
list_unreachable_hosts = _ping.list_unreachable_hosts
|
||||
ping = _ping.ping
|
||||
ping_hosts = _ping.ping_hosts
|
||||
ping_until_delivered = _ping.ping_until_delivered
|
||||
ping_until_undelivered = _ping.ping_until_undelivered
|
||||
ping_until_received = _ping.ping_until_received
|
||||
ping_until_unreceived = _ping.ping_until_unreceived
|
||||
|
||||
TRANSMITTED = _ping.TRANSMITTED
|
||||
UNDELIVERED = _ping.UNDELIVERED
|
||||
DELIVERED = _ping.DELIVERED
|
||||
|
@ -15,24 +15,22 @@
|
||||
# under the License.
|
||||
from __future__ import absolute_import
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
import tobiko
|
||||
from tobiko.shell.ping import _ping
|
||||
|
||||
|
||||
def assert_reachable_ips(target_ips, **params):
|
||||
unreachable_ips = get_unreachable_ips(target_ips, **params)
|
||||
if unreachable_ips:
|
||||
tobiko.fail("Unable to reach IP address(es): {!r}", unreachable_ips)
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def get_reachable_ips(target_ips, **params):
|
||||
return tobiko.select(address
|
||||
for address in target_ips
|
||||
if _ping.ping(address, **params).received)
|
||||
def assert_reachable_hosts(hosts, **params):
|
||||
unreachable_hosts = _ping.list_unreachable_hosts(hosts, **params)
|
||||
if unreachable_hosts:
|
||||
tobiko.fail("Unable to reach host(s): {!r}", unreachable_hosts)
|
||||
|
||||
|
||||
def get_unreachable_ips(target_ips, **params):
|
||||
reachable_ips = get_reachable_ips(target_ips, **params)
|
||||
return tobiko.select(address
|
||||
for address in target_ips
|
||||
if address not in reachable_ips)
|
||||
def assert_unreachable_hosts(hosts, **params):
|
||||
reachable_hosts = _ping.list_reachable_hosts(hosts, **params)
|
||||
if reachable_hosts:
|
||||
tobiko.fail("Reached host(s): {!r}", reachable_hosts)
|
||||
|
@ -74,7 +74,7 @@ class PingInterfaceManager(tobiko.SharedFixture):
|
||||
interfaces=self.interfaces)
|
||||
interface = interface or self.default_interface
|
||||
LOG.debug('Assign Ping interface %r to SSH client %r',
|
||||
interface.ping_interface_name, ssh_client)
|
||||
interface, ssh_client)
|
||||
self.client_interfaces[ssh_client] = interface
|
||||
return interface
|
||||
|
||||
@ -112,7 +112,6 @@ def ping_interface(interface_class):
|
||||
|
||||
class PingInterface(object):
|
||||
|
||||
ping_interface_name = 'default'
|
||||
ping_usage = None
|
||||
|
||||
def match_ping_usage(self, usage):
|
||||
@ -120,16 +119,23 @@ class PingInterface(object):
|
||||
return False
|
||||
|
||||
def get_ping_command(self, parameters):
|
||||
destination = parameters.host
|
||||
if not destination:
|
||||
host = parameters.host
|
||||
if not host:
|
||||
raise ValueError("Ping host destination hasn't been specified")
|
||||
return ([self.get_ping_executable(parameters)] +
|
||||
self.get_ping_options(parameters) +
|
||||
[destination])
|
||||
|
||||
command = sh.shell_command([self.get_ping_executable(parameters)] +
|
||||
self.get_ping_options(parameters) +
|
||||
[host])
|
||||
LOG.debug('Got ping command from interface %r for host %r: %r',
|
||||
self, host, command)
|
||||
return command
|
||||
|
||||
def get_ping_executable(self, parameters):
|
||||
# pylint: disable=unused-argument
|
||||
return 'ping'
|
||||
ip_version = _parameters.get_ping_ip_version(parameters)
|
||||
if ip_version == constants.IP_VERSION_6:
|
||||
return 'ping6'
|
||||
else:
|
||||
return 'ping'
|
||||
|
||||
def get_ping_options(self, parameters):
|
||||
options = []
|
||||
@ -195,13 +201,16 @@ class PingInterface(object):
|
||||
|
||||
def get_fragment_option(self, fragment):
|
||||
details = ("{!r} ping implementation doesn't support "
|
||||
"'fragment={!r}' option").format(self.ping_interface_name,
|
||||
fragment)
|
||||
"'fragment={!r}' option").format(self, fragment)
|
||||
raise _exception.UnsupportedPingOption(details=details)
|
||||
|
||||
|
||||
class IpVersionPingInterface(PingInterface):
|
||||
|
||||
def get_ping_executable(self, parameters):
|
||||
# pylint: disable=unused-argument
|
||||
return 'ping'
|
||||
|
||||
def get_ipv4_option(self):
|
||||
return ['-4']
|
||||
|
||||
@ -221,8 +230,6 @@ Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface]
|
||||
@ping_interface
|
||||
class IpUtilsPingInterface(PingInterface):
|
||||
|
||||
ping_interface_name = 'iputils'
|
||||
|
||||
def match_ping_usage(self, usage):
|
||||
return usage.startswith(IPUTILS_PING_USAGE)
|
||||
|
||||
@ -253,8 +260,6 @@ Usage: ping -6 [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface]
|
||||
class IpUtilsIpVersionPingInterface(IpUtilsPingInterface,
|
||||
IpVersionPingInterface):
|
||||
|
||||
ping_interface_name = 'ip_version_iputils'
|
||||
|
||||
def match_ping_usage(self, usage):
|
||||
return usage.startswith(IP_VERSION_IPUTILS_PING_USAGE)
|
||||
|
||||
@ -285,8 +290,6 @@ Send ICMP ECHO_REQUEST packets to network hosts
|
||||
@ping_interface
|
||||
class BusyBoxPingInterface(IpVersionPingInterface):
|
||||
|
||||
ping_interface_name = 'BusyBox'
|
||||
|
||||
def match_ping_usage(self, usage):
|
||||
return usage.startswith("BusyBox")
|
||||
|
||||
@ -339,13 +342,6 @@ class InetToolsPingInterface(PingInterface):
|
||||
def match_ping_usage(self, usage):
|
||||
return usage.startswith(INET_TOOLS_PING_USAGE)
|
||||
|
||||
def get_ping_executable(self, parameters):
|
||||
ip_version = _parameters.get_ping_ip_version(parameters)
|
||||
if ip_version == constants.IP_VERSION_6:
|
||||
return 'ping6'
|
||||
else:
|
||||
return 'ping'
|
||||
|
||||
def get_deadline_option(self, parameters):
|
||||
ip_version = _parameters.get_ping_ip_version(parameters)
|
||||
if ip_version == constants.IP_VERSION_6:
|
||||
|
@ -26,7 +26,7 @@ LOG = log.getLogger(__name__)
|
||||
|
||||
PING_PARAMETER_NAMES = ['host', 'count', 'deadline', 'fragmentation',
|
||||
'interval', 'ip_version', 'packet_size', 'source',
|
||||
'timeout']
|
||||
'timeout', 'network_namespace']
|
||||
|
||||
|
||||
def get_ping_parameters(default=None, **ping_params):
|
||||
@ -45,7 +45,7 @@ def get_ping_parameters(default=None, **ping_params):
|
||||
def ping_parameters(default=None, count=None, deadline=None,
|
||||
fragmentation=None, host=None, interval=None,
|
||||
ip_version=None, packet_size=None, source=None,
|
||||
timeout=None):
|
||||
timeout=None, network_namespace=None):
|
||||
"""Validate parameters and initialize a new PingParameters instance
|
||||
|
||||
:param default: (PingParameters or None) instance from where to take
|
||||
@ -105,7 +105,9 @@ def ping_parameters(default=None, count=None, deadline=None,
|
||||
ip_version=get_positive_integer('ip_version', ip_version, default),
|
||||
packet_size=get_positive_integer('packet_size', packet_size, default),
|
||||
source=get_address('source', source, default),
|
||||
timeout=get_positive_integer('timeout', timeout, default))
|
||||
timeout=get_positive_integer('timeout', timeout, default),
|
||||
network_namespace=get_string('network_namespace', network_namespace,
|
||||
default))
|
||||
|
||||
|
||||
def default_ping_parameters():
|
||||
@ -196,6 +198,14 @@ def get_address(name, value, default=None):
|
||||
return value
|
||||
|
||||
|
||||
def get_string(name, value, default=None):
|
||||
if value is None and default:
|
||||
return get_string(name, getattr(default, name))
|
||||
if value is not None:
|
||||
value = str(value)
|
||||
return value
|
||||
|
||||
|
||||
IP_HEADER_SIZE = {4: 20, 6: 40}
|
||||
ICMP_HEADER_SIZE = {4: 8, 6: 4}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import time
|
||||
from oslo_log import log
|
||||
|
||||
|
||||
import tobiko
|
||||
from tobiko.shell import sh
|
||||
from tobiko.shell.ping import _interface
|
||||
from tobiko.shell.ping import _exception
|
||||
@ -37,6 +38,33 @@ RECEIVED = 'received'
|
||||
UNRECEIVED = 'unreceived'
|
||||
|
||||
|
||||
def list_reachable_hosts(hosts, **params):
|
||||
reachable_host, _ = ping_hosts(hosts, **params)
|
||||
return reachable_host
|
||||
|
||||
|
||||
def list_unreachable_hosts(hosts, **params):
|
||||
_, unreachable_host = ping_hosts(hosts, **params)
|
||||
return unreachable_host
|
||||
|
||||
|
||||
def ping_hosts(hosts, **params):
|
||||
reachable = tobiko.Selection()
|
||||
unreachable = tobiko.Selection()
|
||||
for host in hosts:
|
||||
try:
|
||||
result = ping(host, count=1, **params)
|
||||
except _exception.PingError:
|
||||
LOG.exception('Error pinging host: %r', host)
|
||||
unreachable.append(host)
|
||||
else:
|
||||
if result.received:
|
||||
reachable.append(host)
|
||||
else:
|
||||
unreachable.append(host)
|
||||
return reachable, unreachable
|
||||
|
||||
|
||||
def ping(host, until=TRANSMITTED, check=True, **ping_params):
|
||||
"""Send ICMP messages to host address until timeout
|
||||
|
||||
@ -200,12 +228,18 @@ def iter_statistics(parameters=None, ssh_client=None, until=None, check=True,
|
||||
transmitted += statistics.transmitted
|
||||
received += statistics.received
|
||||
undelivered += statistics.undelivered
|
||||
count = {None: 0,
|
||||
TRANSMITTED: transmitted,
|
||||
DELIVERED: transmitted - undelivered,
|
||||
UNDELIVERED: undelivered,
|
||||
RECEIVED: received,
|
||||
UNRECEIVED: transmitted - received}[until]
|
||||
else:
|
||||
# Assume 1 transmitted undelivered package when unable to get
|
||||
# ping output
|
||||
transmitted += 1
|
||||
undelivered += 1
|
||||
|
||||
count = {None: 0,
|
||||
TRANSMITTED: transmitted,
|
||||
DELIVERED: transmitted - undelivered,
|
||||
UNDELIVERED: undelivered,
|
||||
RECEIVED: received,
|
||||
UNRECEIVED: transmitted - received}[until]
|
||||
|
||||
now = time.time()
|
||||
deadline = min(int(end_of_time - now), parameters.deadline)
|
||||
@ -228,7 +262,8 @@ def execute_ping(parameters, ssh_client=None, check=True):
|
||||
result = sh.execute(command=command,
|
||||
ssh_client=ssh_client,
|
||||
timeout=parameters.deadline + 2.,
|
||||
expect_exit_status=None)
|
||||
expect_exit_status=None,
|
||||
network_namespace=parameters.network_namespace)
|
||||
except sh.ShellError as ex:
|
||||
LOG.exception("Error executing ping command")
|
||||
stdout = ex.stdout
|
||||
|
19
tobiko/tests/functional/shell/fixtures.py
Normal file
19
tobiko/tests/functional/shell/fixtures.py
Normal file
@ -0,0 +1,19 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import tobiko
|
||||
from tobiko.openstack import topology
|
||||
from tobiko.shell import ip
|
||||
|
||||
|
||||
class NetworkNamespaceFixture(tobiko.SharedFixture):
|
||||
|
||||
network_namespace = None
|
||||
ssh_client = None
|
||||
|
||||
def setup_fixture(self):
|
||||
for node in topology.list_openstack_nodes():
|
||||
network_namespaces = ip.list_network_namespaces(
|
||||
ssh_client=node.ssh_client)
|
||||
if network_namespaces:
|
||||
self.network_namespace = network_namespaces.first
|
||||
self.ssh_client = node.ssh_client
|
@ -21,9 +21,9 @@ import testtools
|
||||
|
||||
import tobiko
|
||||
from tobiko.openstack import stacks
|
||||
from tobiko.openstack import topology
|
||||
from tobiko.shell import ip
|
||||
from tobiko.shell import ssh
|
||||
from tobiko.tests.functional.shell import fixtures
|
||||
|
||||
|
||||
class IpTest(testtools.TestCase):
|
||||
@ -37,6 +37,8 @@ class IpTest(testtools.TestCase):
|
||||
ubuntu_stack = tobiko.required_setup_fixture(
|
||||
stacks.UbuntuServerStackFixture)
|
||||
|
||||
namespace = tobiko.required_setup_fixture(fixtures.NetworkNamespaceFixture)
|
||||
|
||||
def test_list_ip_addresses(self, ip_version=None, scope=None,
|
||||
**execute_params):
|
||||
ips = ip.list_ip_addresses(ip_version=ip_version, scope=scope,
|
||||
@ -100,23 +102,19 @@ class IpTest(testtools.TestCase):
|
||||
self, **execute_params):
|
||||
self.test_list_ip_addresses(scope='global', **execute_params)
|
||||
|
||||
def test_list_ip_addresses_with_namespace(self):
|
||||
for node in topology.list_openstack_nodes():
|
||||
network_namespaces = ip.list_network_namespaces(
|
||||
ssh_client=node.ssh_client)
|
||||
if network_namespaces:
|
||||
network_namespace = network_namespaces.first
|
||||
ssh_client = node.ssh_client
|
||||
break
|
||||
|
||||
def test_list_ip_addresses_with_namespace(self, **params):
|
||||
namespace_ips = ip.list_ip_addresses(
|
||||
ssh_client=ssh_client, scope='global',
|
||||
network_namespace=network_namespace)
|
||||
ssh_client=self.namespace.ssh_client, **params,
|
||||
network_namespace=self.namespace.network_namespace)
|
||||
self.assertNotEqual([], namespace_ips)
|
||||
|
||||
host_ips = ip.list_ip_addresses(ssh_client=ssh_client, scope='global')
|
||||
host_ips = ip.list_ip_addresses(ssh_client=self.namespace.ssh_client,
|
||||
**params)
|
||||
self.assertNotEqual(host_ips, namespace_ips)
|
||||
|
||||
def test_list_ip_addresses_with_namespace_and_scope(self):
|
||||
self.test_list_ip_addresses_with_namespace(scope='global')
|
||||
|
||||
def test_list_namespaces(self, **execute_params):
|
||||
namespaces = ip.list_network_namespaces(**execute_params)
|
||||
self.assertIsInstance(namespaces, list)
|
||||
|
@ -18,8 +18,11 @@ from __future__ import absolute_import
|
||||
import netaddr
|
||||
import testtools
|
||||
|
||||
import tobiko
|
||||
from tobiko import config
|
||||
from tobiko.shell import ip
|
||||
from tobiko.shell import ping
|
||||
from tobiko.tests.functional.shell import fixtures
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
@ -27,6 +30,8 @@ CONF = config.CONF
|
||||
|
||||
class PingTest(testtools.TestCase):
|
||||
|
||||
namespace = tobiko.required_setup_fixture(fixtures.NetworkNamespaceFixture)
|
||||
|
||||
def test_ping_recheable_address(self):
|
||||
result = ping.ping('127.0.0.1', count=3)
|
||||
self.assertIsNone(result.source)
|
||||
@ -90,3 +95,21 @@ class PingTest(testtools.TestCase):
|
||||
self.assertEqual(1., ex.timeout)
|
||||
self.assertEqual(20, ex.expected_count)
|
||||
self.assertEqual('transmitted', ex.message_type)
|
||||
|
||||
def test_ping_hosts(self, ssh_client=None, network_namespace=None,
|
||||
**params):
|
||||
ips = ip.list_ip_addresses(ssh_client=ssh_client,
|
||||
network_namespace=network_namespace)
|
||||
reachable_ips, unrecheable_ips = ping.ping_hosts(
|
||||
ips, ssh_client=ssh_client, network_namespace=network_namespace,
|
||||
**params)
|
||||
|
||||
expected_reachable = [i for i in ips if i in reachable_ips]
|
||||
self.assertEqual(expected_reachable, reachable_ips)
|
||||
expected_unreachable = [i for i in ips if i not in reachable_ips]
|
||||
self.assertEqual(expected_unreachable, unrecheable_ips)
|
||||
|
||||
def test_ping_hosts_from_network_namespace(self):
|
||||
self.test_ping_hosts(
|
||||
ssh_client=self.namespace.ssh_client,
|
||||
network_namespace=self.namespace.network_namespace)
|
||||
|
@ -51,8 +51,8 @@ class PortTest(testtools.TestCase):
|
||||
subnets = neutron.list_subnets(network_id=network_id)
|
||||
gateway_ips = [netaddr.IPAddress(subnet['gateway_ip'])
|
||||
for subnet in subnets]
|
||||
ping.assert_reachable_ips(gateway_ips,
|
||||
ssh_client=self.stack.ssh_client)
|
||||
ping.assert_reachable_hosts(gateway_ips,
|
||||
ssh_client=self.stack.ssh_client)
|
||||
|
||||
def test_ping_port(self, network_id=None, device_id=None):
|
||||
network_id = network_id or self.stack.network_stack.network_id
|
||||
@ -64,8 +64,8 @@ class PortTest(testtools.TestCase):
|
||||
self.assertEqual(network_id, port['network_id'])
|
||||
self.assertEqual(device_id, port['device_id'])
|
||||
port_ips.update(neutron.list_port_ip_addresses(port=port))
|
||||
ping.assert_reachable_ips(port_ips,
|
||||
ssh_client=self.stack.ssh_client)
|
||||
ping.assert_reachable_hosts(port_ips,
|
||||
ssh_client=self.stack.ssh_client)
|
||||
|
||||
def test_ping_inner_gateway_ip(self):
|
||||
if not self.stack.network_stack.has_gateway:
|
||||
|
@ -69,12 +69,12 @@ class RouterTest(testtools.TestCase):
|
||||
self.external_gateway_ips)
|
||||
|
||||
def test_internal_router_ipv4_interface_is_reachable(self):
|
||||
ping.assert_reachable_ips([self.ipv4_subnet_gateway_ip],
|
||||
ssh_client=self.stack.ssh_client)
|
||||
ping.assert_reachable_hosts([self.ipv4_subnet_gateway_ip],
|
||||
ssh_client=self.stack.ssh_client)
|
||||
|
||||
def test_internal_router_ipv6_interface_is_reachable(self):
|
||||
ping.assert_reachable_ips([self.ipv6_subnet_gateway_ip],
|
||||
ssh_client=self.stack.ssh_client)
|
||||
ping.assert_reachable_hosts([self.ipv6_subnet_gateway_ip],
|
||||
ssh_client=self.stack.ssh_client)
|
||||
|
||||
def test_ipv4_subnet_gateway_ip(self):
|
||||
self.assertEqual(4, self.ipv4_subnet_gateway_ip.version)
|
||||
|
Loading…
Reference in New Issue
Block a user