Merge "Disable QoS scenario tests differently"
diff --git a/neutron/tests/tempest/README.rst b/neutron/tests/tempest/README.rst
index d61e72c..f27c34e 100644
--- a/neutron/tests/tempest/README.rst
+++ b/neutron/tests/tempest/README.rst
@@ -1,10 +1,9 @@
 WARNING
 =======
 
-The files under this path were copied from tempest as part of the move
-of the api tests, and they will be removed as required over time to
-minimize the dependency on the tempest testing framework.
-While it exists, only neutron.tests.tempest.api and neutron.tests.retargetable
-should be importing files from this path.  neutron.tests.tempest.config uses
-the global cfg.CONF instance and importing it outside of the api tests
-has the potential to break Neutron's use of cfg.CONF.
+Some files under this path were copied from tempest as part of the move of the
+api tests, and they will be removed as required over time to minimize the
+dependency on the tempest testing framework.  While it exists, only
+neutron.tests.tempest.* should be importing files from this path.
+neutron.tests.tempest.config uses the global cfg.CONF instance and importing it
+outside of the api tests has the potential to break Neutron's use of cfg.CONF.
diff --git a/neutron/tests/tempest/api/admin/test_routers_ha.py b/neutron/tests/tempest/api/admin/test_routers_ha.py
new file mode 100644
index 0000000..187eb6d
--- /dev/null
+++ b/neutron/tests/tempest/api/admin/test_routers_ha.py
@@ -0,0 +1,93 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest import test
+
+from neutron.tests.tempest.api import base_routers as base
+
+
+class RoutersTestHA(base.BaseRouterTest):
+
+    @classmethod
+    @test.requires_ext(extension="router", service="network")
+    @test.requires_ext(extension="l3-ha", service="network")
+    def resource_setup(cls):
+        # The check above will pass if api_extensions=all, which does
+        # not mean "l3-ha" extension itself is present.
+        # Instead, we have to check whether "ha" is actually present by using
+        # admin credentials to create router with ha=True attribute
+        # and checking for BadRequest exception and that the resulting router
+        # has a high availability attribute.
+        super(RoutersTestHA, cls).resource_setup()
+        name = data_utils.rand_name('pretest-check')
+        router = cls.admin_client.create_router(name)
+        if 'ha' not in router['router']:
+            cls.admin_client.delete_router(router['router']['id'])
+            msg = "'ha' attribute not found. HA Possibly not enabled"
+            raise cls.skipException(msg)
+
+    @decorators.idempotent_id('8abc177d-14f1-4018-9f01-589b299cbee1')
+    def test_ha_router_creation(self):
+        """
+        Test uses administrative credentials to create a
+        HA (High Availability) router using the ha=True.
+
+        Acceptance
+        The router is created and the "ha" attribute is set to True
+        """
+        name = data_utils.rand_name('router')
+        router = self.admin_client.create_router(name, ha=True)
+        self.addCleanup(self.admin_client.delete_router,
+                        router['router']['id'])
+        self.assertTrue(router['router']['ha'])
+
+    @decorators.idempotent_id('97b5f7ef-2192-4fa3-901e-979cd5c1097a')
+    def test_legacy_router_creation(self):
+        """
+        Test uses administrative credentials to create a
+        SF (Single Failure) router using the ha=False.
+
+        Acceptance
+        The router is created and the "ha" attribute is
+        set to False, thus making it a "Single Failure Router"
+        as opposed to a "High Availability Router"
+        """
+        name = data_utils.rand_name('router')
+        router = self.admin_client.create_router(name, ha=False)
+        self.addCleanup(self.admin_client.delete_router,
+                        router['router']['id'])
+        self.assertFalse(router['router']['ha'])
+
+    @decorators.idempotent_id('5a6bfe82-5b23-45a4-b027-5160997d4753')
+    def test_legacy_router_update_to_ha(self):
+        """
+        Test uses administrative credentials to create a
+        SF (Single Failure) router using the ha=False.
+        Then it will "update" the router ha attribute to True
+
+        Acceptance
+        The router is created and the "ha" attribute is
+        set to False. Once the router is updated, the ha
+        attribute will be set to True
+        """
+        name = data_utils.rand_name('router')
+        # router needs to be in admin state down in order to be upgraded to HA
+        router = self.admin_client.create_router(name, ha=False,
+                                                 admin_state_up=False)
+        self.addCleanup(self.admin_client.delete_router,
+                        router['router']['id'])
+        self.assertFalse(router['router']['ha'])
+        router = self.admin_client.update_router(router['router']['id'],
+                                                 ha=True)
+        self.assertTrue(router['router']['ha'])
diff --git a/neutron/tests/tempest/api/admin/test_shared_network_extension.py b/neutron/tests/tempest/api/admin/test_shared_network_extension.py
index 042a73e..5557c24 100644
--- a/neutron/tests/tempest/api/admin/test_shared_network_extension.py
+++ b/neutron/tests/tempest/api/admin/test_shared_network_extension.py
@@ -50,6 +50,10 @@
         self.assertNotEmpty(items)
         self.assertTrue(all(n['shared'] == shared for n in items))
 
+    def _list_subnets_ids(self, client, shared):
+        body = client.list_subnets(shared=shared)
+        return [subnet['id'] for subnet in body['subnets']]
+
     @decorators.idempotent_id('6661d219-b96d-4597-ad10-51672353421a')
     def test_filtering_shared_subnets(self):
         # shared subnets need to be tested because their shared status isn't
@@ -59,7 +63,8 @@
         priv = self.create_subnet(reg, client=self.client)
         shared = self.create_subnet(self.shared_network,
                                     client=self.admin_client)
-        self.assertIn(shared, self.client.list_subnets(shared=True)['subnets'])
+        self.assertIn(shared['id'],
+                      self._list_subnets_ids(self.client, shared=True))
         self.assertIn(shared,
             self.admin_client.list_subnets(shared=True)['subnets'])
         self.assertNotIn(priv,
@@ -67,8 +72,8 @@
         self.assertNotIn(priv,
             self.admin_client.list_subnets(shared=True)['subnets'])
         self.assertIn(priv, self.client.list_subnets(shared=False)['subnets'])
-        self.assertIn(priv,
-            self.admin_client.list_subnets(shared=False)['subnets'])
+        self.assertIn(priv['id'],
+                      self._list_subnets_ids(self.admin_client, shared=False))
         self.assertNotIn(shared,
             self.client.list_subnets(shared=False)['subnets'])
         self.assertNotIn(shared,
diff --git a/neutron/tests/tempest/api/test_qos.py b/neutron/tests/tempest/api/test_qos.py
index 7280297..287207e 100644
--- a/neutron/tests/tempest/api/test_qos.py
+++ b/neutron/tests/tempest/api/test_qos.py
@@ -82,7 +82,8 @@
     def test_policy_update(self):
         policy = self.create_qos_policy(name='test-policy',
                                         description='',
-                                        shared=False)
+                                        shared=False,
+                                        tenant_id=self.admin_client.tenant_id)
         self.admin_client.update_qos_policy(policy['id'],
                                             description='test policy desc2',
                                             shared=True)
@@ -119,7 +120,8 @@
     def test_shared_policy_update(self):
         policy = self.create_qos_policy(name='test-policy',
                                         description='',
-                                        shared=True)
+                                        shared=True,
+                                        tenant_id=self.admin_client.tenant_id)
 
         self.admin_client.update_qos_policy(policy['id'],
                                             description='test policy desc2')
@@ -606,7 +608,8 @@
     def test_policy_sharing_with_wildcard(self):
         qos_pol = self.create_qos_policy(
             name=data_utils.rand_name('test-policy'),
-            description='test-shared-policy', shared=False)
+            description='test-shared-policy', shared=False,
+            tenant_id=self.admin_client.tenant_id)
         self.assertNotIn(qos_pol, self.client2.list_qos_policies()['policies'])
 
         # test update shared False -> True
diff --git a/neutron/tests/tempest/api/test_revisions.py b/neutron/tests/tempest/api/test_revisions.py
index 3db39c6..7a6443c 100644
--- a/neutron/tests/tempest/api/test_revisions.py
+++ b/neutron/tests/tempest/api/test_revisions.py
@@ -313,17 +313,21 @@
 
     @decorators.idempotent_id('afb6486c-41b5-483e-a500-3c506f4deb49')
     @test.requires_ext(extension="router", service="network")
-    @test.requires_ext(extension="dvr", service="network")
+    @test.requires_ext(extension="l3-ha", service="network")
     def test_update_router_extra_attributes_bumps_revision(self):
-        router = self.create_router(router_name='r1')
+        # updates from CVR to CVR-HA are supported on every release,
+        # but only the admin can forcibly create a non-HA router
+        router_args = {'tenant_id': self.client.tenant_id,
+                       'ha': False}
+        router = self.admin_client.create_router('r1', True,
+            **router_args)['router']
         self.addCleanup(self.client.delete_router, router['id'])
         self.assertIn('revision_number', router)
         rev1 = router['revision_number']
         router = self.admin_client.update_router(
             router['id'], admin_state_up=False)['router']
         self.assertGreater(router['revision_number'], rev1)
-        self.admin_client.update_router(router['id'],
-                                        distributed=True)['router']
+        self.admin_client.update_router(router['id'], ha=True)['router']
         updated = self.client.show_router(router['id'])['router']
         self.assertGreater(updated['revision_number'],
                            router['revision_number'])
diff --git a/neutron/tests/tempest/api/test_routers.py b/neutron/tests/tempest/api/test_routers.py
index 7a1e458..85bfe82 100644
--- a/neutron/tests/tempest/api/test_routers.py
+++ b/neutron/tests/tempest/api/test_routers.py
@@ -259,6 +259,26 @@
         self.assertNotIn('distributed', show_body['router'])
 
 
+class HaRoutersTest(base_routers.BaseRouterTest):
+
+    @classmethod
+    @test.requires_ext(extension="l3-ha", service="network")
+    def skip_checks(cls):
+        super(HaRoutersTest, cls).skip_checks()
+
+    @decorators.idempotent_id('77db8eae-3aa3-4e61-bf2a-e739ce042e53')
+    def test_convert_legacy_router(self):
+        router = self._create_router(data_utils.rand_name('router'))
+        self.assertNotIn('ha', router)
+        update_body = self.admin_client.update_router(router['id'],
+                                                      ha=True)
+        self.assertTrue(update_body['router']['ha'])
+        show_body = self.admin_client.show_router(router['id'])
+        self.assertTrue(show_body['router']['ha'])
+        show_body = self.client.show_router(router['id'])
+        self.assertNotIn('ha', show_body['router'])
+
+
 class RoutersSearchCriteriaTest(base.BaseSearchCriteriaTest):
 
     resource = 'router'
diff --git a/neutron/tests/tempest/api/test_routers_negative.py b/neutron/tests/tempest/api/test_routers_negative.py
index 049b9c8..b97e30d 100644
--- a/neutron/tests/tempest/api/test_routers_negative.py
+++ b/neutron/tests/tempest/api/test_routers_negative.py
@@ -85,3 +85,18 @@
         with testtools.ExpectedException(lib_exc.Forbidden):
             self.create_router(
                 data_utils.rand_name('router'), distributed=True)
+
+
+class HaRoutersNegativeTest(RoutersNegativeTestBase):
+
+    @classmethod
+    @test.requires_ext(extension="l3-ha", service="network")
+    def skip_checks(cls):
+        super(HaRoutersNegativeTest, cls).skip_checks()
+
+    @test.attr(type='negative')
+    @decorators.idempotent_id('821b85b9-9c51-40f3-831f-bf223a7e0084')
+    def test_router_create_tenant_ha_returns_forbidden(self):
+        with testtools.ExpectedException(lib_exc.Forbidden):
+            self.create_router(
+                data_utils.rand_name('router'), ha=True)
diff --git a/neutron/tests/tempest/api/test_tag.py b/neutron/tests/tempest/api/test_tag.py
index 70a2d44..2553dfb 100644
--- a/neutron/tests/tempest/api/test_tag.py
+++ b/neutron/tests/tempest/api/test_tag.py
@@ -44,6 +44,10 @@
         self.client.update_tag(self.resource, self.res_id, 'red')
         self._get_and_compare_tags(['red', 'blue', 'green'])
 
+        # add a tag with a dot
+        self.client.update_tag(self.resource, self.res_id, 'black.or.white')
+        self._get_and_compare_tags(['red', 'blue', 'green', 'black.or.white'])
+
         # replace tags
         tags = ['red', 'yellow', 'purple']
         res_body = self.client.update_tags(self.resource, self.res_id, tags)
diff --git a/neutron/tests/tempest/scenario/base.py b/neutron/tests/tempest/scenario/base.py
index 967a45e..1e20c74 100644
--- a/neutron/tests/tempest/scenario/base.py
+++ b/neutron/tests/tempest/scenario/base.py
@@ -36,24 +36,17 @@
     def resource_setup(cls):
         super(BaseTempestTestCase, cls).resource_setup()
 
-        cls.servers = []
         cls.keypairs = []
 
     @classmethod
     def resource_cleanup(cls):
-        for server in cls.servers:
-            cls.manager.servers_client.delete_server(server)
-            waiters.wait_for_server_termination(cls.manager.servers_client,
-                                                server)
-
         for keypair in cls.keypairs:
             cls.manager.keypairs_client.delete_keypair(
                 keypair_name=keypair['name'])
 
         super(BaseTempestTestCase, cls).resource_cleanup()
 
-    @classmethod
-    def create_server(cls, flavor_ref, image_ref, key_name, networks,
+    def create_server(self, flavor_ref, image_ref, key_name, networks,
                       name=None, security_groups=None):
         """Create a server using tempest lib
         All the parameters are the ones used in Compute API
@@ -78,14 +71,20 @@
         if not security_groups:
             security_groups = [{'name': 'default'}]
 
-        server = cls.manager.servers_client.create_server(
+        server = self.manager.servers_client.create_server(
             name=name,
             flavorRef=flavor_ref,
             imageRef=image_ref,
             key_name=key_name,
             networks=networks,
             security_groups=security_groups)
-        cls.servers.append(server['server']['id'])
+
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+            waiters.wait_for_server_termination,
+            self.manager.servers_client, server['server']['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.manager.servers_client.delete_server,
+                        server['server']['id'])
         return server
 
     @classmethod
@@ -155,50 +154,48 @@
         cls.routers.append(router)
         return router
 
-    @classmethod
-    def create_and_associate_floatingip(cls, port_id):
-        fip = cls.manager.network_client.create_floatingip(
+    def create_and_associate_floatingip(self, port_id):
+        fip = self.manager.network_client.create_floatingip(
             CONF.network.public_network_id,
             port_id=port_id)['floatingip']
-        cls.floating_ips.append(fip)
+        self.floating_ips.append(fip)
         return fip
 
-    @classmethod
-    def setup_network_and_server(cls, router=None, **kwargs):
+    def setup_network_and_server(self, router=None, **kwargs):
         """Create network resources and a server.
 
         Creating a network, subnet, router, keypair, security group
         and a server.
         """
-        cls.network = cls.create_network()
-        LOG.debug("Created network %s", cls.network['name'])
-        cls.subnet = cls.create_subnet(cls.network)
-        LOG.debug("Created subnet %s", cls.subnet['id'])
+        self.network = self.create_network()
+        LOG.debug("Created network %s", self.network['name'])
+        self.subnet = self.create_subnet(self.network)
+        LOG.debug("Created subnet %s", self.subnet['id'])
 
-        secgroup = cls.manager.network_client.create_security_group(
+        secgroup = self.manager.network_client.create_security_group(
             name=data_utils.rand_name('secgroup-'))
         LOG.debug("Created security group %s",
                   secgroup['security_group']['name'])
-        cls.security_groups.append(secgroup['security_group'])
+        self.security_groups.append(secgroup['security_group'])
         if not router:
-            router = cls.create_router_by_client(**kwargs)
-        cls.create_router_interface(router['id'], cls.subnet['id'])
-        cls.keypair = cls.create_keypair()
-        cls.create_loginable_secgroup_rule(
+            router = self.create_router_by_client(**kwargs)
+        self.create_router_interface(router['id'], self.subnet['id'])
+        self.keypair = self.create_keypair()
+        self.create_loginable_secgroup_rule(
             secgroup_id=secgroup['security_group']['id'])
-        cls.server = cls.create_server(
+        self.server = self.create_server(
             flavor_ref=CONF.compute.flavor_ref,
             image_ref=CONF.compute.image_ref,
-            key_name=cls.keypair['name'],
-            networks=[{'uuid': cls.network['id']}],
+            key_name=self.keypair['name'],
+            networks=[{'uuid': self.network['id']}],
             security_groups=[{'name': secgroup['security_group']['name']}])
-        waiters.wait_for_server_status(cls.manager.servers_client,
-                                       cls.server['server']['id'],
+        waiters.wait_for_server_status(self.manager.servers_client,
+                                       self.server['server']['id'],
                                        constants.SERVER_STATUS_ACTIVE)
-        port = cls.client.list_ports(network_id=cls.network['id'],
-                                     device_id=cls.server[
+        port = self.client.list_ports(network_id=self.network['id'],
+                                     device_id=self.server[
                                           'server']['id'])['ports'][0]
-        cls.fip = cls.create_and_associate_floatingip(port['id'])
+        self.fip = self.create_and_associate_floatingip(port['id'])
 
     def check_connectivity(self, host, ssh_user, ssh_key, servers=None):
         ssh_client = ssh.Client(host, ssh_user, pkey=ssh_key)
diff --git a/neutron/tests/tempest/scenario/test_floatingip.py b/neutron/tests/tempest/scenario/test_floatingip.py
index d4ad72a..1ccb6ac 100644
--- a/neutron/tests/tempest/scenario/test_floatingip.py
+++ b/neutron/tests/tempest/scenario/test_floatingip.py
@@ -51,15 +51,10 @@
         cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id'])
         cls.create_pingable_secgroup_rule(secgroup_id=cls.secgroup['id'])
 
-        cls._src_server = cls._create_server()
         if cls.same_network:
             cls._dest_network = cls.network
         else:
             cls._dest_network = cls._create_dest_network()
-        cls._dest_server_with_fip = cls._create_server(
-            network=cls._dest_network)
-        cls._dest_server_without_fip = cls._create_server(
-            create_floating_ip=False, network=cls._dest_network)
 
     @classmethod
     def _create_dest_network(cls):
@@ -69,28 +64,27 @@
         cls.create_router_interface(cls.router['id'], subnet['id'])
         return network
 
-    @classmethod
-    def _create_server(cls, create_floating_ip=True, network=None):
+    def _create_server(self, create_floating_ip=True, network=None):
         if network is None:
-            network = cls.network
-        port = cls.create_port(network, security_groups=[cls.secgroup['id']])
+            network = self.network
+        port = self.create_port(network, security_groups=[self.secgroup['id']])
         if create_floating_ip:
-            fip = cls.create_and_associate_floatingip(port['id'])
+            fip = self.create_and_associate_floatingip(port['id'])
         else:
             fip = None
-        server = cls.create_server(
+        server = self.create_server(
             flavor_ref=CONF.compute.flavor_ref,
             image_ref=CONF.compute.image_ref,
-            key_name=cls.keypair['name'],
+            key_name=self.keypair['name'],
             networks=[{'port': port['id']}])['server']
-        waiters.wait_for_server_status(cls.manager.servers_client,
+        waiters.wait_for_server_status(self.manager.servers_client,
                                        server['id'],
                                        constants.SERVER_STATUS_ACTIVE)
         return {'port': port, 'fip': fip, 'server': server}
 
     def _test_east_west(self):
         # Source VM
-        server1 = self._src_server
+        server1 = self._create_server()
         server1_ip = server1['fip']['floating_ip_address']
         ssh_client = ssh.Client(server1_ip,
                                 CONF.validation.image_ssh_user,
@@ -98,9 +92,10 @@
 
         # Destination VM
         if self.dest_has_fip:
-            dest_server = self._dest_server_with_fip
+            dest_server = self._create_server(network=self._dest_network)
         else:
-            dest_server = self._dest_server_without_fip
+            dest_server = self._create_server(create_floating_ip=False,
+                                              network=self._dest_network)
 
         # Check connectivity
         self.check_remote_connectivity(ssh_client,
diff --git a/neutron/tests/tempest/scenario/test_trunk.py b/neutron/tests/tempest/scenario/test_trunk.py
index 122068c..c857f3f 100644
--- a/neutron/tests/tempest/scenario/test_trunk.py
+++ b/neutron/tests/tempest/scenario/test_trunk.py
@@ -239,7 +239,8 @@
             # Configure VLAN interfaces on server
             command = CONFIGURE_VLAN_INTERFACE_COMMANDS % {'tag': vlan_tag}
             server['ssh_client'].exec_command(command)
-            out = server['ssh_client'].exec_command('ip addr list')
+            out = server['ssh_client'].exec_command(
+                'PATH=$PATH:/usr/sbin;ip addr list')
             LOG.debug("Interfaces on server %s: %s", server, out)
 
         # Ping from server1 to server2 via VLAN interface should fail because