diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py index 52ccfa9601..e9fcd4bed0 100644 --- a/tempest/common/utils/linux/remote_client.py +++ b/tempest/common/utils/linux/remote_client.py @@ -155,3 +155,53 @@ class RemoteClient(remote_client.RemoteClient): 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 0743220c14..c017762d67 100644 --- a/tempest/config.py +++ b/tempest/config.py @@ -1020,7 +1020,12 @@ ScenarioGroup = [ 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 6a12b59e5b..a47b6f2422 100644 --- a/tempest/scenario/manager.py +++ b/tempest/scenario/manager.py @@ -881,24 +881,33 @@ class NetworkScenarioTest(ScenarioTest): 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 e39afe0212..bfe3604726 100644 --- a/tempest/scenario/test_security_groups_basic_ops.py +++ b/tempest/scenario/test_security_groups_basic_ops.py @@ -395,24 +395,22 @@ class TestSecurityGroupsBasicOps(manager.NetworkScenarioTest): 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 @@ class TestSecurityGroupsBasicOps(manager.NetworkScenarioTest): 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 @@ class TestSecurityGroupsBasicOps(manager.NetworkScenarioTest): 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 @@ class TestSecurityGroupsBasicOps(manager.NetworkScenarioTest): 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 @@ class TestSecurityGroupsBasicOps(manager.NetworkScenarioTest): # 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