Adds protocol options for test_cross_tenant_traffic
Hyper-V cannot create stateful ICMP / ICMPv6 rules, but can create
stateful rules for any other protocol, thus, the test
test_cross_tenant_traffic cannot pass in the Hyper-V CI at the moment.
Testing other protocols instead can solve this issue.
Adds configuration option scenario.protocols, which contains the protocols
tested by the mentioned test. Its default option is 'icmp', in order
to maintain existing behaviour.
Change-Id: Ia304b81ec60a4fb06730f297e732a6b19a183f7f
Closes-Bug: #1363986
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 52ccfa9..e9fcd4b 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -155,3 +155,53 @@
cmd_why = 'sudo ls -lR /dev'
LOG.info("Contents of /dev: %s", self.exec_command(cmd_why))
raise
+
+ def nc_listen_host(self, port=80, protocol='tcp'):
+ """Creates persistent nc server listening on the given TCP / UDP port
+
+ :port: the port to start listening on.
+ :protocol: the protocol used by the server. TCP by default.
+ """
+ udp = '-u' if protocol.lower() == 'udp' else ''
+ cmd = "sudo nc %(udp)s -p %(port)s -lk -e echo foolish &" % {
+ 'udp': udp, 'port': port}
+ return self.exec_command(cmd)
+
+ def nc_host(self, host, port=80, protocol='tcp', expected_response=None):
+ """Check connectivity to TCP / UDP port at host via nc
+
+ :host: an IP against which the connectivity will be tested.
+ :port: the port to check connectivity against.
+ :protocol: the protocol used by nc to send packets. TCP by default.
+ :expected_response: string representing the expected response
+ from server.
+ :raises SSHExecCommandFailed: if an expected response is given and it
+ does not match the actual server response.
+ """
+ udp = '-u' if protocol.lower() == 'udp' else ''
+ cmd = 'echo "bar" | nc -w 1 %(udp)s %(host)s %(port)s' % {
+ 'udp': udp, 'host': host, 'port': port}
+ response = self.exec_command(cmd)
+
+ # sending an UDP packet will always succeed. we need to check
+ # the response.
+ if (expected_response is not None and
+ expected_response != response.strip()):
+ raise tempest.lib.exceptions.SSHExecCommandFailed(
+ command=cmd, exit_status=0, stdout=response, stderr='')
+ return response
+
+ def icmp_check(self, host, nic=None):
+ """Wrapper for icmp connectivity checks"""
+ return self.ping_host(host, nic=nic)
+
+ def udp_check(self, host, **kwargs):
+ """Wrapper for udp connectivity checks."""
+ kwargs.pop('nic', None)
+ return self.nc_host(host, protocol='udp', expected_response='foolish',
+ **kwargs)
+
+ def tcp_check(self, host, **kwargs):
+ """Wrapper for tcp connectivity checks."""
+ kwargs.pop('nic', None)
+ return self.nc_host(host, **kwargs)
diff --git a/tempest/config.py b/tempest/config.py
index 0743220..c017762 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -1020,7 +1020,12 @@
choices=["udhcpc", "dhclient", ""],
help='DHCP client used by images to renew DCHP lease. '
'If left empty, update operation will be skipped. '
- 'Supported clients: "udhcpc", "dhclient"')
+ 'Supported clients: "udhcpc", "dhclient"'),
+ cfg.StrOpt('protocol',
+ default='icmp',
+ choices=('icmp', 'tcp', 'udp'),
+ help='The protocol used in security groups tests to check '
+ 'connectivity.'),
]
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 6a12b59..a47b6f2 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -881,24 +881,33 @@
raise
def check_remote_connectivity(self, source, dest, should_succeed=True,
- nic=None):
- """assert ping server via source ssh connection
+ nic=None, protocol='icmp'):
+ """check server connectivity via source ssh connection
- :param source: RemoteClient: an ssh connection from which to ping
- :param dest: an IP to ping against
- :param should_succeed: boolean: should ping succeed or not
- :param nic: specific network interface to ping from
+ :param source: RemoteClient: an ssh connection from which to execute
+ the check
+ :param dest: an IP to check connectivity against
+ :param should_succeed: boolean should connection succeed or not
+ :param nic: specific network interface to test connectivity from
+ :param protocol: the protocol used to test connectivity with.
+ :returns: True, if the connection succeeded and it was expected to
+ succeed. False otherwise.
"""
- def ping_remote():
+ method_name = '%s_check' % protocol
+ connectivity_checker = getattr(source, method_name)
+
+ def connect_remote():
try:
- source.ping_host(dest, nic=nic)
+ connectivity_checker(dest, nic=nic)
except lib_exc.SSHExecCommandFailed:
- LOG.warning('Failed to ping IP: %s via a ssh connection '
- 'from: %s.', dest, source.ssh_client.host)
+ LOG.warning('Failed to check %(protocol)s connectivity for '
+ 'IP %(dest)s via a ssh connection from: %(src)s.',
+ dict(protocol=protocol, dest=dest,
+ src=source.ssh_client.host))
return not should_succeed
return should_succeed
- result = test_utils.call_until_true(ping_remote,
+ result = test_utils.call_until_true(connect_remote,
CONF.validation.ping_timeout, 1)
source_host = source.ssh_client.host
if should_succeed:
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index e39afe0..bfe3604 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -395,24 +395,22 @@
self.check_remote_connectivity(source=access_point_ssh,
dest=self._get_server_ip(server))
- def _test_cross_tenant_block(self, source_tenant, dest_tenant):
+ def _test_cross_tenant_block(self, source_tenant, dest_tenant, ruleset):
# if public router isn't defined, then dest_tenant access is via
# floating-ip
+ protocol = ruleset['protocol']
access_point_ssh = self._connect_to_access_point(source_tenant)
ip = self._get_server_ip(dest_tenant.access_point,
floating=self.floating_ip_access)
self.check_remote_connectivity(source=access_point_ssh, dest=ip,
- should_succeed=False)
+ should_succeed=False, protocol=protocol)
- def _test_cross_tenant_allow(self, source_tenant, dest_tenant):
+ def _test_cross_tenant_allow(self, source_tenant, dest_tenant, ruleset):
"""check for each direction:
creating rule for tenant incoming traffic enables only 1way traffic
"""
- ruleset = dict(
- protocol='icmp',
- direction='ingress'
- )
+ protocol = ruleset['protocol']
sec_group_rules_client = (
dest_tenant.manager.security_group_rules_client)
self._create_security_group_rule(
@@ -423,10 +421,10 @@
access_point_ssh = self._connect_to_access_point(source_tenant)
ip = self._get_server_ip(dest_tenant.access_point,
floating=self.floating_ip_access)
- self.check_remote_connectivity(access_point_ssh, ip)
+ self.check_remote_connectivity(access_point_ssh, ip, protocol=protocol)
# test that reverse traffic is still blocked
- self._test_cross_tenant_block(dest_tenant, source_tenant)
+ self._test_cross_tenant_block(dest_tenant, source_tenant, ruleset)
# allow reverse traffic and check
sec_group_rules_client = (
@@ -440,7 +438,8 @@
access_point_ssh_2 = self._connect_to_access_point(dest_tenant)
ip = self._get_server_ip(source_tenant.access_point,
floating=self.floating_ip_access)
- self.check_remote_connectivity(access_point_ssh_2, ip)
+ self.check_remote_connectivity(access_point_ssh_2, ip,
+ protocol=protocol)
def _verify_mac_addr(self, tenant):
"""Verify that VM has the same ip, mac as listed in port"""
@@ -470,6 +469,17 @@
self._log_console_output(
servers=[tenant.access_point], client=client)
+ def _create_protocol_ruleset(self, protocol, port=80):
+ if protocol == 'icmp':
+ ruleset = dict(protocol='icmp',
+ direction='ingress')
+ else:
+ ruleset = dict(protocol=protocol,
+ port_range_min=port,
+ port_range_max=port,
+ direction='ingress')
+ return ruleset
+
@decorators.idempotent_id('e79f879e-debb-440c-a7e4-efeda05b6848')
@utils.services('compute', 'network')
def test_cross_tenant_traffic(self):
@@ -484,8 +494,18 @@
# cross tenant check
source_tenant = self.primary_tenant
dest_tenant = self.alt_tenant
- self._test_cross_tenant_block(source_tenant, dest_tenant)
- self._test_cross_tenant_allow(source_tenant, dest_tenant)
+
+ protocol = CONF.scenario.protocol
+ LOG.debug("Testing cross tenant traffic for %s protocol",
+ protocol)
+ if protocol in ['udp', 'tcp']:
+ for tenant in [source_tenant, dest_tenant]:
+ access_point = self._connect_to_access_point(tenant)
+ access_point.nc_listen_host(protocol=protocol)
+
+ ruleset = self._create_protocol_ruleset(protocol)
+ self._test_cross_tenant_block(source_tenant, dest_tenant, ruleset)
+ self._test_cross_tenant_allow(source_tenant, dest_tenant, ruleset)
except Exception:
self._log_console_output_for_all_tenants()
raise