Merge "Adding new test of security group rules quota increased."
diff --git a/.zuul.yaml b/.zuul.yaml
index ceb7bc2..2e99198 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -80,6 +80,7 @@
         - subnet_allocation
         - subnet-dns-publish-fixed-ip
         - subnetpool-prefix-ops
+        - tag-ports-during-bulk-creation
         - trunk
         - trunk-details
         - uplink-status-propagation
@@ -104,13 +105,14 @@
         neutron-network-segment-range: true
         neutron-port-forwarding: true
         neutron-conntrack-helper: true
+        neutron-tag-ports-during-bulk-creation: true
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
             QUOTAS:
               quota_router: 100
               quota_floatingip: 500
-              quota_security_group: 100
+              quota_security_group: 150
               quota_security_group_rule: 1000
           # NOTE(slaweq): We can get rid of this hardcoded absolute path when
           # devstack-tempest job will be switched to use lib/neutron instead of
@@ -251,6 +253,12 @@
     description: |
       This job run on py2 for stable/rocky gate.
     override-checkout: stable/rocky
+    required-projects: &required-projects-rocky
+      - openstack/devstack-gate
+      - openstack/neutron
+      - name: openstack/neutron-tempest-plugin
+        override-checkout: 0.9.0
+      - openstack/tempest
     vars: &api_vars_rocky
       branch_override: stable/rocky
       # TODO(slaweq): find a way to put this list of extensions in
@@ -331,6 +339,7 @@
       This job run on py3 for other than stable/rocky gate
       which is nothing but neutron-tempest-pluign master gate.
     override-checkout: stable/rocky
+    required-projects: *required-projects-rocky
     vars:
       <<: *api_vars_rocky
       devstack_localrc:
@@ -575,6 +584,7 @@
       This job run on py2 for stable/rocky gate.
     nodeset: openstack-single-node-xenial
     override-checkout: stable/rocky
+    required-projects: *required-projects-rocky
     vars: &scenario_vars_rocky
       branch_override: stable/rocky
       network_api_extensions: *api_extensions_rocky
@@ -582,6 +592,10 @@
         USE_PYTHON3: false
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+      # NOTE(bcafarel): newer tests, unstable on rocky branch
+      tempest_black_regex: "\
+          (^neutron_tempest_plugin.scenario.test_port_forwardings.PortForwardingTestJSON.test_port_forwarding_to_2_servers)|\
+          (^neutron_tempest_plugin.scenario.test_security_groups.NetworkSecGroupTest.test_multiple_ports_portrange_remote)"
     branches:
       - stable/rocky
 
@@ -593,6 +607,7 @@
       This job run on py3 for other than stable/rocky gate
       which is nothing but neutron-tempest-pluign master gate.
     override-checkout: stable/rocky
+    required-projects: *required-projects-rocky
     vars:
       <<: *scenario_vars_rocky
       devstack_localrc:
@@ -659,6 +674,7 @@
     description: |
       This job run on py2 for stable/rocky gate.
     override-checkout: stable/rocky
+    required-projects: *required-projects-rocky
     vars: &openvswitch_vars_rocky
       branch_override: stable/rocky
       network_api_extensions: *api_extensions_rocky
@@ -666,6 +682,13 @@
         USE_PYTHON3: false
         NETWORK_API_EXTENSIONS: "{{ network_api_extensions | join(',') }}"
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+      # TODO(bcafarel): remove trunks subport_connectivity test from blacklist
+      # when bug https://bugs.launchpad.net/neutron/+bug/1838760 will be fixed
+      # NOTE(bcafarel): other are newer tests, unstable on rocky branch
+      tempest_black_regex: "\
+          (^neutron_tempest_plugin.scenario.test_trunk.TrunkTest.test_subport_connectivity)|\
+          (^neutron_tempest_plugin.scenario.test_port_forwardings.PortForwardingTestJSON.test_port_forwarding_to_2_servers)|\
+          (^neutron_tempest_plugin.scenario.test_security_groups.NetworkSecGroupTest.test_multiple_ports_portrange_remote)"
     branches:
       - stable/rocky
 
@@ -677,6 +700,7 @@
       This job run on py3 for other than stable/rocky gate
       which is nothing but neutron-tempest-pluign master gate.
     override-checkout: stable/rocky
+    required-projects: *required-projects-rocky
     vars:
       <<: *openvswitch_vars_rocky
       devstack_localrc:
@@ -769,6 +793,7 @@
       This job run on py2 for stable/rocky gate.
     nodeset: openstack-single-node-xenial
     override-checkout: stable/rocky
+    required-projects: *required-projects-rocky
     vars: &linuxbridge_vars_rocky
       branch_override: stable/rocky
       network_api_extensions: *api_extensions_rocky
@@ -784,6 +809,10 @@
           $TEMPEST_CONFIG:
             neutron_plugin_options:
               q_agent: None
+      # NOTE(bcafarel): newer tests, unstable on rocky branch
+      tempest_black_regex: "\
+          (^neutron_tempest_plugin.scenario.test_port_forwardings.PortForwardingTestJSON.test_port_forwarding_to_2_servers)|\
+          (^neutron_tempest_plugin.scenario.test_security_groups.NetworkSecGroupTest.test_multiple_ports_portrange_remote)"
     branches:
       - stable/rocky
 
@@ -795,6 +824,7 @@
       This job run on py3 for other than stable/rocky gate
       which is nothing but neutron-tempest-pluign master gate.
     override-checkout: stable/rocky
+    required-projects: *required-projects-rocky
     vars:
       <<: *linuxbridge_vars_rocky
       devstack_localrc:
@@ -985,12 +1015,17 @@
       This job run on py2 for stable/rocky gate.
     nodeset: openstack-two-node-xenial
     override-checkout: stable/rocky
+    required-projects: *required-projects-rocky
     vars: &multinode_scenario_vars_rocky
       branch_override: stable/rocky
       network_api_extensions_common: *api_extensions_rocky
       devstack_localrc:
         USE_PYTHON3: false
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
+      # NOTE(bcafarel): newer tests, unstable on rocky branch
+      tempest_black_regex: "\
+          (^neutron_tempest_plugin.scenario.test_port_forwardings.PortForwardingTestJSON.test_port_forwarding_to_2_servers)|\
+          (^neutron_tempest_plugin.scenario.test_security_groups.NetworkSecGroupTest.test_multiple_ports_portrange_remote)"
     branches:
       - stable/rocky
 
@@ -1006,6 +1041,7 @@
       <<: *multinode_scenario_vars_rocky
       devstack_localrc:
         USE_PYTHON3: True
+    required-projects: *required-projects-rocky
     group-vars:
       subnode:
         devstack_localrc:
@@ -1079,6 +1115,8 @@
       - openstack/neutron
       - name: openstack/neutron-tempest-plugin
         override-checkout: 0.3.0
+      - name: openstack/designate-tempest-plugin
+        override-checkout: 0.7.0
       - openstack/tempest
     vars:
       branch_override: stable/queens
@@ -1098,6 +1136,14 @@
       This job run on py2 for stable/rocky gate.
     nodeset: openstack-single-node-xenial
     override-checkout: stable/rocky
+    required-projects:
+      - openstack/devstack-gate
+      - openstack/neutron
+      - name: openstack/neutron-tempest-plugin
+        override-checkout: 0.9.0
+      - name: openstack/designate-tempest-plugin
+        override-checkout: 0.7.0
+      - openstack/tempest
     vars: &designate_scenario_vars_rocky
       branch_override: stable/rocky
       network_api_extensions_common: *api_extensions_rocky
@@ -1113,8 +1159,9 @@
     nodeset: openstack-single-node-xenial
     description: |
       This job run on py3 for other than stable/rocky gate
-      which is nothing but neutron-tempest-pluign master gate.
+      which is nothing but neutron-tempest-plugin master gate.
     override-checkout: stable/rocky
+    required-projects: *required-projects-rocky
     vars:
       <<: *designate_scenario_vars_rocky
       devstack_localrc:
@@ -1125,6 +1172,13 @@
     name: neutron-tempest-plugin-designate-scenario-stein
     parent: neutron-tempest-plugin-designate-scenario
     override-checkout: stable/stein
+    required-projects:
+      - openstack/devstack-gate
+      - openstack/neutron
+      - openstack/neutron-tempest-plugin
+      - name: openstack/designate-tempest-plugin
+        override-checkout: 0.7.0
+      - openstack/tempest
     vars:
       branch_override: stable/stein
       network_api_extensions_common: *api_extensions_stein
@@ -1363,9 +1417,6 @@
     templates:
       - build-openstack-docs-pti
       - neutron-tempest-plugin-jobs
-      # TODO(slaweq): bring rocky jobs back when dropping py27
-      # drama will be finally over
-      # - neutron-tempest-plugin-jobs-rocky
       - neutron-tempest-plugin-jobs-stein
       - neutron-tempest-plugin-jobs-train
       - check-requirements
diff --git a/neutron_tempest_plugin/api/test_ports.py b/neutron_tempest_plugin/api/test_ports.py
index 52783b9..8867eee 100644
--- a/neutron_tempest_plugin/api/test_ports.py
+++ b/neutron_tempest_plugin/api/test_ports.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import copy
+
 from tempest.common import utils
 from tempest.lib import decorators
 
@@ -203,3 +205,62 @@
     @decorators.idempotent_id('74293e59-d794-4a93-be09-38667199ef68')
     def test_list_pagination_page_reverse_with_href_links(self):
         self._test_list_pagination_page_reverse_with_href_links()
+
+
+class PortsTaggingOnCreationTestJSON(base.BaseNetworkTest):
+
+    _tags = [
+        ['tag-1', 'tag-2', 'tag-3'],
+        ['tag-1', 'tag-2'],
+        ['tag-1', 'tag-3'],
+        []
+    ]
+
+    @classmethod
+    def resource_setup(cls):
+        super(PortsTaggingOnCreationTestJSON, cls).resource_setup()
+        cls.network = cls.create_network()
+
+    def _create_ports_in_bulk(self, ports):
+        body = self.client.create_bulk_port(ports)
+        for port in body['ports']:
+            self.ports.append(port)
+        return body
+
+    def _create_ports_list(self):
+        num_ports = len(self._tags)
+        net_id = self.network['id']
+        port = {'port': {'network_id': net_id,
+                         'admin_state_up': True}}
+        return [copy.deepcopy(port) for x in range(num_ports)]
+
+    @decorators.idempotent_id('5cf26014-fdd3-4a6d-b94d-a05f0c55da89')
+    @utils.requires_ext(extension="tag-ports-during-bulk-creation",
+                        service="network")
+    def test_tagging_ports_during_bulk_creation(self):
+        ports = self._create_ports_list()
+        ports_tags_map = {}
+        for port, tags in zip(ports, self._tags):
+            port['port']['tags'] = tags
+            port['port']['name'] = '-'.join(tags)
+            ports_tags_map[port['port']['name']] = tags
+        body = self._create_ports_in_bulk(ports)
+        for port in body['ports']:
+            self.assertEqual(ports_tags_map[port['name']], port['tags'])
+
+    @decorators.idempotent_id('33eda785-a08a-44a0-1bbb-fb50a2f1cd78')
+    @utils.requires_ext(extension="tag-ports-during-bulk-creation",
+                        service="network")
+    def test_tagging_ports_during_bulk_creation_no_tags(self):
+        ports = self._create_ports_list()
+        body = self._create_ports_in_bulk(ports)
+        for port in body['ports']:
+            self.assertFalse(port['tags'])
+
+    @decorators.idempotent_id('6baa43bf-88fb-8bca-6051-97ea1a5e8f4f')
+    @utils.requires_ext(extension="tag-ports-during-bulk-creation",
+                        service="network")
+    def test_tagging_ports_during_creation(self):
+        port = {'name': 'port', 'tags': self._tags[0]}
+        body = self.create_port(self.network, **port)
+        self.assertEqual(self._tags[0], body['tags'])
diff --git a/neutron_tempest_plugin/api/test_security_groups.py b/neutron_tempest_plugin/api/test_security_groups.py
index b3ec7ec..a11a031 100644
--- a/neutron_tempest_plugin/api/test_security_groups.py
+++ b/neutron_tempest_plugin/api/test_security_groups.py
@@ -80,6 +80,39 @@
         self.assertIn(
             security_group_rule['id'], observerd_security_group_rules_ids)
 
+    @decorators.idempotent_id('b5923b1a-4d33-44e1-af25-088dcb55b02b')
+    def test_list_security_group_rules_contains_all_rules(self):
+        """Test list security group rules.
+
+        This test checks if all SG rules which belongs to the tenant OR
+        which belongs to the tenant's security group are listed.
+        """
+        security_group = self.create_security_group()
+        protocol = random.choice(list(base_security_groups.V4_PROTOCOL_NAMES))
+        security_group_rule = self.create_security_group_rule(
+            security_group=security_group,
+            project={'id': self.admin_client.tenant_id},
+            client=self.admin_client,
+            protocol=protocol,
+            direction=constants.INGRESS_DIRECTION)
+
+        # Create also other SG with some custom rule to check that regular user
+        # can't see this rule
+        admin_security_group = self.create_security_group(
+            project={'id': self.admin_client.tenant_id},
+            client=self.admin_client)
+        admin_security_group_rule = self.create_security_group_rule(
+            security_group=admin_security_group,
+            project={'id': self.admin_client.tenant_id},
+            client=self.admin_client,
+            protocol=protocol,
+            direction=constants.INGRESS_DIRECTION)
+
+        rules = self.client.list_security_group_rules()['security_group_rules']
+        rules_ids = [rule['id'] for rule in rules]
+        self.assertIn(security_group_rule['id'], rules_ids)
+        self.assertNotIn(admin_security_group_rule['id'], rules_ids)
+
     @decorators.idempotent_id('7c0ecb10-b2db-11e6-9b14-000c29248b0d')
     def test_create_bulk_sec_groups(self):
         # Creates 2 sec-groups in one request
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/admin/test_floatingip.py b/neutron_tempest_plugin/scenario/admin/test_floatingip.py
index 511452c..a08acc3 100644
--- a/neutron_tempest_plugin/scenario/admin/test_floatingip.py
+++ b/neutron_tempest_plugin/scenario/admin/test_floatingip.py
@@ -85,7 +85,7 @@
             server_ssh_clients.append(ssh.Client(
                 fips[i]['floating_ip_address'], CONF.validation.image_ssh_user,
                 pkey=self.keypair['private_key']))
-        return server_ssh_clients, fips
+        return servers, server_ssh_clients, fips
 
     @decorators.idempotent_id('6bba729b-3fb6-494b-9e1e-82bbd89a1045')
     def test_two_vms_fips(self):
@@ -99,6 +99,7 @@
         hyper = self._list_hypervisors()[0]['hypervisor_hostname']
         # Get availability zone list to pass it for vm creation
         avail_zone = self._list_availability_zones()[0]['zoneName']
-        server_ssh_clients, fips = self._create_vms(hyper, avail_zone)
+        servers, server_ssh_clients, fips = self._create_vms(hyper, avail_zone)
         self.check_remote_connectivity(
-            server_ssh_clients[0], fips[1]['floating_ip_address'])
+            server_ssh_clients[0], fips[1]['floating_ip_address'],
+            servers=servers)
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index 42bd33b..7b66494 100644
--- a/neutron_tempest_plugin/scenario/base.py
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -12,6 +12,8 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+import distutils
+import re
 import subprocess
 
 from debtcollector import removals
@@ -30,6 +32,7 @@
 from neutron_tempest_plugin.common import shell
 from neutron_tempest_plugin.common import ssh
 from neutron_tempest_plugin import config
+from neutron_tempest_plugin import exceptions
 from neutron_tempest_plugin.scenario import constants
 
 CONF = config.CONF
@@ -37,6 +40,45 @@
 LOG = log.getLogger(__name__)
 
 
+def get_ncat_version(ssh_client=None):
+    cmd = "ncat --version 2>&1"
+    try:
+        version_result = shell.execute(cmd, ssh_client=ssh_client).stdout
+    except exceptions.ShellCommandFailed:
+        m = None
+    else:
+        m = re.match(r"Ncat: Version ([\d.]+) *.", version_result)
+    # NOTE(slaweq): by default lets assume we have ncat 7.60 which is in Ubuntu
+    # 18.04 which is used on u/s gates
+    return distutils.version.StrictVersion(m.group(1) if m else '7.60')
+
+
+def get_ncat_server_cmd(port, protocol, msg):
+    udp = ''
+    if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
+        udp = '-u'
+    cmd = "nc %(udp)s -p %(port)s -lk " % {
+        'udp': udp, 'port': port}
+    if CONF.neutron_plugin_options.default_image_is_advanced:
+        cmd += "-c 'echo %s' &" % msg
+    else:
+        cmd += "-e echo %s &" % msg
+    return cmd
+
+
+def get_ncat_client_cmd(ip_address, port, protocol):
+    udp = ''
+    if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
+        udp = '-u'
+    cmd = 'echo "knock knock" | nc '
+    ncat_version = get_ncat_version()
+    if ncat_version > distutils.version.StrictVersion('7.60'):
+        cmd += '-z '
+    cmd += '-w 1 %(udp)s %(host)s %(port)s' % {
+        'udp': udp, 'host': ip_address, 'port': port}
+    return cmd
+
+
 class BaseTempestTestCase(base_api.BaseNetworkTest):
 
     def create_server(self, flavor_ref, image_ref, key_name, networks,
@@ -426,13 +468,10 @@
 
         Listener is created always on remote host.
         """
-        udp = ''
-        if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
-            udp = '-u'
-        cmd = "sudo nc %(udp)s -p %(port)s -lk -c echo %(msg)s &" % {
-            'udp': udp, 'port': port, 'msg': echo_msg}
         try:
-            return ssh_client.exec_command(cmd)
+            return ssh_client.execute_script(
+                get_ncat_server_cmd(port, protocol, echo_msg),
+                become_root=True)
         except lib_exc.SSHTimeout as ssh_e:
             LOG.debug(ssh_e)
             self._log_console_output([server])
@@ -443,11 +482,7 @@
 
         Client is always executed locally on host where tests are executed.
         """
-        udp = ''
-        if protocol.lower() == neutron_lib_constants.PROTO_NAME_UDP:
-            udp = '-u'
-        cmd = 'echo "knock knock" | nc -w 1 %(udp)s %(host)s %(port)s' % {
-            'udp': udp, 'host': ip_address, 'port': port}
+        cmd = get_ncat_client_cmd(ip_address, port, protocol)
         result = shell.execute_local_command(cmd)
         self.assertEqual(0, result.exit_status)
         return result.stdout
diff --git a/neutron_tempest_plugin/scenario/test_connectivity.py b/neutron_tempest_plugin/scenario/test_connectivity.py
index 5aa8f73..1a7468a 100644
--- a/neutron_tempest_plugin/scenario/test_connectivity.py
+++ b/neutron_tempest_plugin/scenario/test_connectivity.py
@@ -66,6 +66,8 @@
         for vm in vms:
             self.wait_for_server_active(vm['server'])
 
+        return vms
+
     @decorators.idempotent_id('8944b90d-1766-4669-bd8a-672b5d106bb7')
     def test_connectivity_through_2_routers(self):
         ap1_net = self.create_network()
@@ -109,7 +111,7 @@
             routes=[{"destination": ap1_subnet['cidr'],
                      "nexthop": ap1_wan_port['fixed_ips'][0]['ip_address']}])
 
-        self._create_servers(ap1_internal_port, ap2_internal_port)
+        servers = self._create_servers(ap1_internal_port, ap2_internal_port)
 
         ap1_fip = self.create_and_associate_floatingip(
             ap1_internal_port['id'])
@@ -118,7 +120,8 @@
             pkey=self.keypair['private_key'])
 
         self.check_remote_connectivity(
-            ap1_sshclient, ap2_internal_port['fixed_ips'][0]['ip_address'])
+            ap1_sshclient, ap2_internal_port['fixed_ips'][0]['ip_address'],
+            servers=servers)
 
     @decorators.idempotent_id('b72c3b77-3396-4144-b05d-9cd3c0099893')
     def test_connectivity_router_east_west_traffic(self):
@@ -145,7 +148,7 @@
         self.create_router_interface(router['id'], subnet_1['id'])
         self.create_router_interface(router['id'], subnet_2['id'])
 
-        self._create_servers(internal_port_1, internal_port_2)
+        servers = self._create_servers(internal_port_1, internal_port_2)
 
         fip = self.create_and_associate_floatingip(
             internal_port_1['id'])
@@ -155,7 +158,7 @@
 
         self.check_remote_connectivity(
             sshclient, internal_port_2['fixed_ips'][0]['ip_address'],
-            ping_count=10)
+            ping_count=10, servers=servers)
 
     @utils.requires_ext(extension="dvr", service="network")
     @decorators.idempotent_id('69d3650a-5c32-40bc-ae56-5c4c849ddd37')
@@ -237,7 +240,8 @@
             fip['floating_ip_address'], CONF.validation.image_ssh_user,
             pkey=self.keypair['private_key'])
 
-        self.check_remote_connectivity(sshclient, str(gw_ip), ping_count=10)
+        self.check_remote_connectivity(
+            sshclient, str(gw_ip), ping_count=10, servers=[vm])
         self.check_remote_connectivity(
             sshclient, dvr_router_port['fixed_ips'][0]['ip_address'],
-            ping_count=10)
+            ping_count=10, servers=[vm])
diff --git a/neutron_tempest_plugin/scenario/test_floatingip.py b/neutron_tempest_plugin/scenario/test_floatingip.py
index e276a02..37df5be 100644
--- a/neutron_tempest_plugin/scenario/test_floatingip.py
+++ b/neutron_tempest_plugin/scenario/test_floatingip.py
@@ -134,10 +134,12 @@
 
         # Check connectivity
         self.check_remote_connectivity(ssh_client,
-            dest_server['port']['fixed_ips'][0]['ip_address'])
+            dest_server['port']['fixed_ips'][0]['ip_address'],
+            servers=[src_server, dest_server])
         if self.dest_has_fip:
             self.check_remote_connectivity(ssh_client,
-                dest_server['fip']['floating_ip_address'])
+                dest_server['fip']['floating_ip_address'],
+                servers=[src_server, dest_server])
 
 
 class FloatingIpSameNetwork(FloatingIpTestCasesMixin,
@@ -200,7 +202,8 @@
                                 pkey=self.keypair['private_key'],
                                 proxy_client=proxy_client)
         self.check_remote_connectivity(ssh_client,
-                                       gateway_external_ip)
+                                       gateway_external_ip,
+                                       servers=[proxy, src_server])
 
 
 class FloatingIPPortDetailsTest(FloatingIpTestCasesMixin,
@@ -418,7 +421,8 @@
         self.fip = self.create_floatingip(port=ports[0])
         self.check_connectivity(self.fip['floating_ip_address'],
                                 CONF.validation.image_ssh_user,
-                                self.keypair['private_key'])
+                                self.keypair['private_key'],
+                                servers=servers)
         self.client.update_floatingip(self.fip['id'], port_id=ports[1]['id'])
 
         def _wait_for_fip_associated():
diff --git a/neutron_tempest_plugin/scenario/test_internal_dns.py b/neutron_tempest_plugin/scenario/test_internal_dns.py
index 13ca797..d19286c 100644
--- a/neutron_tempest_plugin/scenario/test_internal_dns.py
+++ b/neutron_tempest_plugin/scenario/test_internal_dns.py
@@ -69,11 +69,14 @@
         # in very long boot times.
         self.check_remote_connectivity(
             ssh_client, leia_port['fixed_ips'][0]['ip_address'],
-            timeout=CONF.validation.ping_timeout * 10)
+            timeout=CONF.validation.ping_timeout * 10,
+            servers=[self.server, leia])
 
         resolv_conf = ssh_client.exec_command('cat /etc/resolv.conf')
         self.assertIn('openstackgate.local', resolv_conf)
         self.assertNotIn('starwars', resolv_conf)
 
-        self.check_remote_connectivity(ssh_client, 'leia')
-        self.check_remote_connectivity(ssh_client, 'leia.openstackgate.local')
+        self.check_remote_connectivity(ssh_client, 'leia',
+                                       servers=[self.server, leia])
+        self.check_remote_connectivity(ssh_client, 'leia.openstackgate.local',
+                                       servers=[self.server, leia])
diff --git a/neutron_tempest_plugin/scenario/test_mtu.py b/neutron_tempest_plugin/scenario/test_mtu.py
index df730c6..31319ec 100644
--- a/neutron_tempest_plugin/scenario/test_mtu.py
+++ b/neutron_tempest_plugin/scenario/test_mtu.py
@@ -129,7 +129,8 @@
         for fip in (fip1, fip2):
             self.check_connectivity(
                 fip['floating_ip_address'],
-                self.username, self.keypair['private_key'])
+                self.username, self.keypair['private_key'],
+                servers=[server1, server2])
         return server_ssh_client1, fip1, server_ssh_client2, fip2
 
     @testtools.skipUnless(
diff --git a/neutron_tempest_plugin/scenario/test_port_forwardings.py b/neutron_tempest_plugin/scenario/test_port_forwardings.py
index 7283887..2d77b65 100644
--- a/neutron_tempest_plugin/scenario/test_port_forwardings.py
+++ b/neutron_tempest_plugin/scenario/test_port_forwardings.py
@@ -14,12 +14,12 @@
 #    under the License.
 
 from neutron_lib import constants
-from neutron_lib.utils import test
 from oslo_log import log
 from tempest.lib.common.utils import data_utils
 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
 
@@ -81,27 +81,32 @@
         return servers
 
     def _test_udp_port_forwarding(self, servers):
+
+        def _message_received(server, ssh_client, expected_msg):
+            self.nc_listen(server,
+                           ssh_client,
+                           server['port_forwarding_udp']['internal_port'],
+                           constants.PROTO_NAME_UDP,
+                           expected_msg)
+            received_msg = self.nc_client(
+                self.fip['floating_ip_address'],
+                server['port_forwarding_udp']['external_port'],
+                constants.PROTO_NAME_UDP)
+            return expected_msg in received_msg
+
         for server in servers:
-            msg = "%s-UDP-test" % server['name']
+            expected_msg = "%s-UDP-test" % server['name']
             ssh_client = ssh.Client(
                 self.fip['floating_ip_address'],
                 CONF.validation.image_ssh_user,
                 pkey=self.keypair['private_key'],
                 port=server['port_forwarding_tcp']['external_port'])
-            self.nc_listen(server,
-                           ssh_client,
-                           server['port_forwarding_udp']['internal_port'],
-                           constants.PROTO_NAME_UDP,
-                           msg)
-        for server in servers:
-            expected_msg = "%s-UDP-test" % server['name']
-            self.assertIn(
-                expected_msg, self.nc_client(
-                    self.fip['floating_ip_address'],
-                    server['port_forwarding_udp']['external_port'],
-                    constants.PROTO_NAME_UDP))
+            utils.wait_until_true(
+                lambda: _message_received(server, ssh_client, expected_msg),
+                exception=RuntimeError(
+                    "Timed out waiting for message from server {!r} ".format(
+                        server['id'])))
 
-    @test.unstable_test("bug 1850800")
     @decorators.idempotent_id('ab40fc48-ca8d-41a0-b2a3-f6679c847bfe')
     def test_port_forwarding_to_2_servers(self):
         udp_sg_rule = {'protocol': constants.PROTO_NAME_UDP,
diff --git a/neutron_tempest_plugin/scenario/test_qos.py b/neutron_tempest_plugin/scenario/test_qos.py
index ba8cc88..e84fb3c 100644
--- a/neutron_tempest_plugin/scenario/test_qos.py
+++ b/neutron_tempest_plugin/scenario/test_qos.py
@@ -19,8 +19,8 @@
 from neutron_lib.services.qos import constants as qos_consts
 from oslo_log import log as logging
 from tempest.common import utils as tutils
+from tempest.common import waiters
 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 +92,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 +122,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()
@@ -160,6 +152,47 @@
                                         shared=True)
         return policy['policy']['id']
 
+    def _create_server_by_port(self, port=None):
+        """Launch an instance using a port interface;
+
+        In case that the given port is None, a new port is created,
+        activated and configured with inbound SSH and TCP connection.
+        """
+        # Create and activate the port that will be assign to the instance.
+        if port is None:
+            secgroup = self.create_security_group()
+            self.create_loginable_secgroup_rule(
+                secgroup_id=secgroup['id'])
+
+            secgroup_rules = [{'protocol': 'tcp',
+                               'direction': 'ingress',
+                               'port_range_min': self.NC_PORT,
+                               'port_range_max': self.NC_PORT,
+                               'remote_ip_prefix': '0.0.0.0/0'}]
+
+            self.create_secgroup_rules(secgroup_rules,
+                                       secgroup['id'])
+
+            port = self.create_port(self.network,
+                                    security_groups=[secgroup['id']])
+            self.fip = self.create_floatingip(port=port)
+
+        keypair = self.create_keypair()
+
+        server_kwargs = {
+            'flavor_ref': CONF.compute.flavor_ref,
+            'image_ref': CONF.compute.image_ref,
+            'key_name': keypair['name'],
+            'networks': [{'port': port['id']}],
+        }
+
+        server = self.create_server(**server_kwargs)
+        self.wait_for_server_active(server['server'])
+        self.check_connectivity(self.fip['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                keypair['private_key'])
+        return server, port
+
 
 class QoSTest(QoSTestMixin, base.BaseTempestTestCase):
     @classmethod
@@ -283,3 +316,58 @@
             port=self.NC_PORT, expected_bw=QoSTest.LIMIT_BYTES_SEC * 3),
             timeout=self.FILE_DOWNLOAD_TIMEOUT,
             sleep=1)
+
+    @decorators.idempotent_id('66e5673e-0522-11ea-8d71-362b9e155667')
+    def test_attach_previously_used_port_to_new_instance(self):
+        """The test spawns new instance using port with QoS policy.
+
+        Ports with attached QoS policy could be used multiple times.
+        The policy rules have to be enforced on the new machines.
+        """
+        self.network = self.create_network()
+        self.subnet = self.create_subnet(self.network)
+        self.router = self.create_router_by_client()
+        self.create_router_interface(self.router['id'], self.subnet['id'])
+
+        vm, vm_port = self._create_server_by_port()
+
+        port_policy = self.os_admin.network_client.create_qos_policy(
+            name='port-policy',
+            description='policy for attach',
+            shared=False)['policy']
+
+        rule = self.os_admin.network_client.create_bandwidth_limit_rule(
+            policy_id=port_policy['id'],
+            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
+            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
+                    'bandwidth_limit_rule']
+
+        self.os_admin.network_client.update_port(
+            vm_port['id'], qos_policy_id=port_policy['id'])
+
+        self.os_primary.servers_client.delete_server(vm['server']['id'])
+        waiters.wait_for_server_termination(
+            self.os_primary.servers_client,
+            vm['server']['id'])
+
+        # Launch a new server using the same port with attached policy
+        self._create_server_by_port(port=vm_port)
+
+        retrieved_port = self.os_admin.network_client.show_port(
+            vm_port['id'])
+        self.assertEqual(port_policy['id'],
+                         retrieved_port['port']['qos_policy_id'],
+                         """The expected policy ID is {0},
+                         the actual value is {1}""".
+                         format(port_policy['id'],
+                                retrieved_port['port']['qos_policy_id']))
+
+        retrieved_policy = self.os_admin.network_client.show_qos_policy(
+                           retrieved_port['port']['qos_policy_id'])
+
+        retrieved_rule_id = retrieved_policy['policy']['rules'][0]['id']
+        self.assertEqual(rule['id'],
+                         retrieved_rule_id,
+                         """The expected rule ID is {0},
+                         the actual value is {1}""".
+                         format(rule['id'], retrieved_rule_id))
diff --git a/neutron_tempest_plugin/scenario/test_security_groups.py b/neutron_tempest_plugin/scenario/test_security_groups.py
index 7b43a7e..dc14857 100644
--- a/neutron_tempest_plugin/scenario/test_security_groups.py
+++ b/neutron_tempest_plugin/scenario/test_security_groups.py
@@ -16,9 +16,11 @@
 
 from tempest.common import waiters
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 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 +32,39 @@
     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 setup_credentials(cls):
+        super(NetworkSecGroupTest, cls).setup_credentials()
+        cls.project_id = cls.os_primary.credentials.tenant_id
+        cls.network_client = cls.os_admin.network_client
+
     @classmethod
     def resource_setup(cls):
         super(NetworkSecGroupTest, cls).resource_setup()
@@ -40,6 +75,12 @@
         cls.create_router_interface(router['id'], cls.subnet['id'])
         cls.keypair = cls.create_keypair()
 
+    def setUp(self):
+        super(NetworkSecGroupTest, self).setUp()
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.network_client.reset_quotas, self.project_id)
+        self.network_client.update_quotas(self.project_id, security_group=-1)
+
     def create_vm_testing_sec_grp(self, num_servers=2, security_groups=None,
                                   ports=None):
         """Create instance for security group testing
@@ -100,11 +141,12 @@
 
         # make sure ICMP connectivity works
         self.check_remote_connectivity(server_ssh_clients[0], fips[1][
-            'fixed_ip_address'], should_succeed=should_succeed)
+            'fixed_ip_address'], should_succeed=should_succeed,
+            servers=servers)
 
     @decorators.idempotent_id('3d73ec1a-2ec6-45a9-b0f8-04a283d9d764')
     def test_default_sec_grp_scenarios(self):
-        server_ssh_clients, fips, _ = self.create_vm_testing_sec_grp()
+        server_ssh_clients, fips, servers = self.create_vm_testing_sec_grp()
         # Check ssh connectivity when you add sec group rule, enabling ssh
         self.create_loginable_secgroup_rule(
             self.os_primary.network_client.list_security_groups()[
@@ -121,7 +163,8 @@
         # Check ICMP connectivity between VMs without specific rule for that
         # It should work though the rule is not configured
         self.check_remote_connectivity(
-            server_ssh_clients[0], fips[1]['fixed_ip_address'])
+            server_ssh_clients[0], fips[1]['fixed_ip_address'],
+            servers=servers)
 
         # Check ICMP connectivity from VM to external network
         subnets = self.os_admin.network_client.list_subnets(
@@ -132,7 +175,8 @@
                 ext_net_ip = subnet['gateway_ip']
                 break
         self.assertTrue(ext_net_ip)
-        self.check_remote_connectivity(server_ssh_clients[0], ext_net_ip)
+        self.check_remote_connectivity(server_ssh_clients[0], ext_net_ip,
+                                       servers=servers)
 
     @decorators.idempotent_id('3d73ec1a-2ec6-45a9-b0f8-04a283d9d864')
     def test_protocol_number_rule(self):
@@ -256,7 +300,8 @@
             rule_list, secgroup_id=ssh_secgrp['security_group']['id'])
         # verify ICMP connectivity between instances works
         self.check_remote_connectivity(
-            server_ssh_clients[0], fips[1]['fixed_ip_address'])
+            server_ssh_clients[0], fips[1]['fixed_ip_address'],
+            servers=servers)
         # make sure ICMP connectivity doesn't work from framework
         self.ping_ip_address(fips[0]['floating_ip_address'],
                              should_succeed=False)
@@ -293,3 +338,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)
diff --git a/neutron_tempest_plugin/scenario/test_trunk.py b/neutron_tempest_plugin/scenario/test_trunk.py
index 6d855f1..7fd6c52 100644
--- a/neutron_tempest_plugin/scenario/test_trunk.py
+++ b/neutron_tempest_plugin/scenario/test_trunk.py
@@ -282,7 +282,8 @@
         self.create_pingable_secgroup_rule(self.security_group['id'])
         self.check_remote_connectivity(
             vm1.ssh_client,
-            vm2.subport['fixed_ips'][0]['ip_address'])
+            vm2.subport['fixed_ips'][0]['ip_address'],
+            servers=[vm1, vm2])
 
     @testtools.skipUnless(
         (CONF.neutron_plugin_options.advanced_image_ref or
@@ -308,11 +309,12 @@
             use_advanced_image=use_advanced_image)
         normal_network_server = self._create_server_with_network(self.network)
         vlan_network_server = self._create_server_with_network(vlan_network)
+        vms = [normal_network_server, vlan_network_server]
 
         self._configure_vlan_subport(vm=trunk_network_server,
                                      vlan_tag=vlan_tag,
                                      vlan_subnet=vlan_subnet)
-        for vm in [normal_network_server, vlan_network_server]:
+        for vm in vms:
             self.wait_for_server_active(vm.server)
 
         # allow ICMP traffic
@@ -323,14 +325,16 @@
         self.check_remote_connectivity(
             trunk_network_server.ssh_client,
             normal_network_server.port['fixed_ips'][0]['ip_address'],
-            should_succeed=True)
+            should_succeed=True,
+            servers=vms)
 
         # Ping from trunk_network_server to vlan_network_server via VLAN
         # interface should success
         self.check_remote_connectivity(
             trunk_network_server.ssh_client,
             vlan_network_server.port['fixed_ips'][0]['ip_address'],
-            should_succeed=True)
+            should_succeed=True,
+            servers=vms)
 
         # Delete the trunk
         self.delete_trunk(
@@ -344,7 +348,8 @@
         self.check_remote_connectivity(
             trunk_network_server.ssh_client,
             normal_network_server.port['fixed_ips'][0]['ip_address'],
-            should_succeed=True)
+            should_succeed=True,
+            servers=vms)
 
         # Ping from trunk_network_server to vlan_network_server via VLAN
         # interface should fail after trunk deleted
diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py
index 521e2be..ddb6f95 100644
--- a/neutron_tempest_plugin/services/network/json/network_client.py
+++ b/neutron_tempest_plugin/services/network/json/network_client.py
@@ -893,6 +893,15 @@
         self.expected_success(204, resp.status)
         return service_client.ResponseBody(resp, body)
 
+    def list_security_group_rules(self, **kwargs):
+        uri = '%s/security-group-rules' % self.uri_prefix
+        if kwargs:
+            uri += '?' + urlparse.urlencode(kwargs, doseq=1)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
     def create_security_group_rule(self, direction, security_group_id,
                                    **kwargs):
         post_body = {'security_group_rule': kwargs}
diff --git a/releasenotes/notes/drop-py-2-7-74b8379cab4cdc5a.yaml b/releasenotes/notes/drop-py-2-7-74b8379cab4cdc5a.yaml
new file mode 100644
index 0000000..7d49171
--- /dev/null
+++ b/releasenotes/notes/drop-py-2-7-74b8379cab4cdc5a.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    Python 2.7 support has been dropped. Last release of neutron-tempest-plugin
+    to support py2.7 is OpenStack Train. The minimum version of Python now
+    supported by neutron-tempest-plugin is Python 3.6.
diff --git a/requirements.txt b/requirements.txt
index 9a5e99f..d3fa3eb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,7 +5,6 @@
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
 neutron-lib>=1.25.0 # Apache-2.0
 oslo.config>=5.2.0 # Apache-2.0
-ipaddress>=1.0.17;python_version<'3.3' # PSF
 netaddr>=0.7.18 # BSD
 os-ken>=0.3.0 # Apache-2.0
 oslo.log>=3.36.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index ff12b10..1ac729c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,6 +6,7 @@
 author = OpenStack
 author-email = openstack-discuss@lists.openstack.org
 home-page = https://opendev.org/openstack/neutron-tempest-plugin
+requires-python = >=3.6
 classifier =
     Environment :: OpenStack
     Intended Audience :: Information Technology
@@ -13,8 +14,6 @@
     License :: OSI Approved :: Apache Software License
     Operating System :: POSIX :: Linux
     Programming Language :: Python
-    Programming Language :: Python :: 2
-    Programming Language :: Python :: 2.7
     Programming Language :: Python :: 3
     Programming Language :: Python :: 3.6
 
diff --git a/test-requirements.txt b/test-requirements.txt
index 8b251f6..905420c 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -7,8 +7,7 @@
 coverage!=4.4,>=4.0 # Apache-2.0
 flake8-import-order==0.12 # LGPLv3
 python-subunit>=1.0.0 # Apache-2.0/BSD
-sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7'  # BSD
-sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4'  # BSD
+sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2  # BSD
 oslotest>=3.2.0 # Apache-2.0
 stestr>=1.0.0 # Apache-2.0
 testtools>=2.2.0 # MIT
diff --git a/tox.ini b/tox.ini
index 95352a2..19e006a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,9 +1,11 @@
 [tox]
-minversion = 2.0
+minversion = 3.1
 envlist = pep8
 skipsdist = True
+ignore_basepython_conflict = True
 
 [testenv]
+basepython = python3
 usedevelop = True
 setenv =
    VIRTUAL_ENV={envdir}
@@ -19,7 +21,6 @@
 commands = stestr run --slowest {posargs}
 
 [testenv:pep8]
-basepython = python3
 commands =
   sh ./tools/misc-sanity-checks.sh
   flake8
@@ -27,11 +28,9 @@
   sh
 
 [testenv:venv]
-basepython = python3
 commands = {posargs}
 
 [testenv:cover]
-basepython = python3
 setenv =
     {[testenv]setenv}
     PYTHON=coverage run --source neutron_tempest_plugin --parallel-mode
@@ -42,16 +41,13 @@
     coverage xml -o cover/coverage.xml
 
 [testenv:docs]
-basepython = python3
 commands = python setup.py build_sphinx
 
 [testenv:releasenotes]
-basepython = python3
 commands =
   sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
 
 [testenv:debug]
-basepython = python3
 commands = oslo_debug_helper -t neutron_tempest_plugin/ {posargs}
 
 [flake8]