Merge "Add port-range remote_group security group testcase"
diff --git a/neutron_tempest_plugin/common/utils.py b/neutron_tempest_plugin/common/utils.py
index 631f75b..c8ff194 100644
--- a/neutron_tempest_plugin/common/utils.py
+++ b/neutron_tempest_plugin/common/utils.py
@@ -26,6 +26,7 @@
     from urllib import parse as urlparse
 
 import eventlet
+from tempest.lib import exceptions
 
 SCHEMA_PORT_MAPPING = {
     "http": 80,
@@ -106,3 +107,22 @@
     if scheme in SCHEMA_PORT_MAPPING and not port:
         netloc = netloc + ":" + str(SCHEMA_PORT_MAPPING[scheme])
     return urlparse.urlunparse((scheme, netloc, url, params, query, fragment))
+
+
+def kill_nc_process(ssh_client):
+    cmd = "killall -q nc"
+    try:
+        ssh_client.exec_command(cmd)
+    except exceptions.SSHExecCommandFailed:
+        pass
+
+
+def spawn_http_server(ssh_client, port, message):
+    cmd = ("(echo -e 'HTTP/1.1 200 OK\r\n'; echo '%(msg)s') "
+           "| sudo nc -lp %(port)d &" % {'msg': message, 'port': port})
+    ssh_client.exec_command(cmd)
+
+
+def call_url_remote(ssh_client, url):
+    cmd = "curl %s --retry 3 --connect-timeout 2" % url
+    return ssh_client.exec_command(cmd)
diff --git a/neutron_tempest_plugin/scenario/test_qos.py b/neutron_tempest_plugin/scenario/test_qos.py
index ba8cc88..f8f1b03 100644
--- a/neutron_tempest_plugin/scenario/test_qos.py
+++ b/neutron_tempest_plugin/scenario/test_qos.py
@@ -20,7 +20,6 @@
 from oslo_log import log as logging
 from tempest.common import utils as tutils
 from tempest.lib import decorators
-from tempest.lib import exceptions
 
 from neutron_tempest_plugin.api import base as base_api
 from neutron_tempest_plugin.common import ssh
@@ -92,16 +91,8 @@
             raise sc_exceptions.FileCreationFailedException(
                 file=self.FILE_PATH)
 
-    @staticmethod
-    def _kill_nc_process(ssh_client):
-        cmd = "killall -q nc"
-        try:
-            ssh_client.exec_command(cmd, timeout=5)
-        except exceptions.SSHExecCommandFailed:
-            pass
-
     def _check_bw(self, ssh_client, host, port, expected_bw=LIMIT_BYTES_SEC):
-        self._kill_nc_process(ssh_client)
+        utils.kill_nc_process(ssh_client)
         cmd = ("(nc -ll -p %(port)d < %(file_path)s > /dev/null &)" % {
                 'port': port, 'file_path': self.FILE_PATH})
         ssh_client.exec_command(cmd, timeout=5)
@@ -130,7 +121,7 @@
         except socket.timeout:
             LOG.warning('Socket timeout while reading the remote file, bytes '
                         'read: %s', total_bytes_read)
-            self._kill_nc_process(ssh_client)
+            utils.kill_nc_process(ssh_client)
             return False
         finally:
             client_socket.close()
diff --git a/neutron_tempest_plugin/scenario/test_security_groups.py b/neutron_tempest_plugin/scenario/test_security_groups.py
index 7b43a7e..83bb55c 100644
--- a/neutron_tempest_plugin/scenario/test_security_groups.py
+++ b/neutron_tempest_plugin/scenario/test_security_groups.py
@@ -19,6 +19,7 @@
 from tempest.lib import decorators
 
 from neutron_tempest_plugin.common import ssh
+from neutron_tempest_plugin.common import utils
 from neutron_tempest_plugin import config
 from neutron_tempest_plugin.scenario import base
 from neutron_tempest_plugin.scenario import constants as const
@@ -30,6 +31,33 @@
     credentials = ['primary', 'admin']
     required_extensions = ['router', 'security-group']
 
+    def _verify_http_connection(self, ssh_client, ssh_server,
+                                test_ip, test_port, should_pass=True):
+        """Verify if HTTP connection works using remote hosts.
+
+        :param ssh.Client ssh_client: The client host active SSH client.
+        :param ssh.Client ssh_server: The HTTP server host active SSH client.
+        :param string test_ip: IP address of HTTP server
+        :param string test_port: Port of HTTP server
+        :param bool should_pass: Wheter test should pass or not.
+
+        :return: if passed or not
+        :rtype: bool
+        """
+        utils.kill_nc_process(ssh_server)
+        url = 'http://%s:%d' % (test_ip, test_port)
+        utils.spawn_http_server(ssh_server, port=test_port, message='foo_ok')
+        try:
+            ret = utils.call_url_remote(ssh_client, url)
+            if should_pass:
+                self.assertIn('foo_ok', ret)
+                return
+            self.assertNotIn('foo_ok', ret)
+        except Exception as e:
+            if not should_pass:
+                return
+            raise e
+
     @classmethod
     def resource_setup(cls):
         super(NetworkSecGroupTest, cls).resource_setup()
@@ -293,3 +321,65 @@
             self.check_connectivity(fip['floating_ip_address'],
                                     CONF.validation.image_ssh_user,
                                     self.keypair['private_key'])
+
+    @decorators.idempotent_id('f07d0159-8f9e-4faa-87f5-a869ab0ad489')
+    def test_multiple_ports_portrange_remote(self):
+        ssh_clients, fips, servers = self.create_vm_testing_sec_grp(
+            num_servers=3)
+        secgroups = []
+        ports = []
+
+        # Create remote and test security groups
+        for i in range(0, 2):
+            secgroups.append(
+                self.create_security_group(name='secgrp-%d' % i))
+            # configure sec groups to support SSH connectivity
+            self.create_loginable_secgroup_rule(
+                secgroup_id=secgroups[-1]['id'])
+
+        # Configure security groups, first two servers as remotes
+        for i, server in enumerate(servers):
+            port = self.client.list_ports(
+                network_id=self.network['id'], device_id=server['server'][
+                    'id'])['ports'][0]
+            ports.append(port)
+            secgroup = secgroups[0 if i in range(0, 2) else 1]
+            self.client.update_port(port['id'], security_groups=[
+                secgroup['id']])
+
+        # verify SSH functionality
+        for fip in fips:
+            self.check_connectivity(fip['floating_ip_address'],
+                                    CONF.validation.image_ssh_user,
+                                    self.keypair['private_key'])
+
+        test_ip = ports[2]['fixed_ips'][0]['ip_address']
+
+        # verify that conections are not working
+        for port in range(80, 84):
+            self._verify_http_connection(
+                ssh_clients[0],
+                ssh_clients[2],
+                test_ip, port,
+                should_pass=False)
+
+        # add two remote-group rules with port-ranges
+        rule_list = [{'protocol': constants.PROTO_NUM_TCP,
+                      'direction': constants.INGRESS_DIRECTION,
+                      'port_range_min': '80',
+                      'port_range_max': '81',
+                      'remote_group_id': secgroups[0]['id']},
+                     {'protocol': constants.PROTO_NUM_TCP,
+                      'direction': constants.INGRESS_DIRECTION,
+                      'port_range_min': '82',
+                      'port_range_max': '83',
+                      'remote_group_id': secgroups[0]['id']}]
+        self.create_secgroup_rules(
+            rule_list, secgroup_id=secgroups[1]['id'])
+
+        # verify that conections are working
+        for port in range(80, 84):
+            self._verify_http_connection(
+                ssh_clients[0],
+                ssh_clients[2],
+                test_ip, port)