Merge "Tests for nova unified quotas"
diff --git a/tempest/scenario/test_compute_unified_limits.py b/tempest/scenario/test_compute_unified_limits.py
new file mode 100644
index 0000000..bacf526
--- /dev/null
+++ b/tempest/scenario/test_compute_unified_limits.py
@@ -0,0 +1,166 @@
+# Copyright 2021 Red Hat, Inc.
+# All Rights Reserved.
+#
+#    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.
+
+import testtools
+
+from tempest.common import utils
+from tempest.common import waiters
+from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest.scenario import manager
+
+CONF = config.CONF
+
+
+@testtools.skipUnless(CONF.compute_feature_enabled.unified_limits,
+                      'Compute unified limits are not enabled')
+class ComputeProjectQuotaTest(manager.ScenarioTest):
+    """The test base class for compute unified limits tests.
+
+    Dynamic credentials (unique tenants) are created on a per-class basis, so
+    we test different quota limits in separate test classes to prevent a quota
+    limit update in one test class from affecting a test running in another
+    test class in parallel.
+
+    https://docs.openstack.org/tempest/latest/configuration.html#dynamic-credentials
+    """
+    credentials = ['primary', 'system_admin']
+    force_tenant_isolation = True
+
+    @classmethod
+    def resource_setup(cls):
+        super(ComputeProjectQuotaTest, cls).resource_setup()
+
+        # Figure out and record the nova service id
+        services = cls.os_system_admin.identity_services_v3_client.\
+            list_services()
+        nova_services = [x for x in services['services']
+                         if x['name'] == 'nova']
+        cls.nova_service_id = nova_services[0]['id']
+
+        # Pre-create quota limits in subclasses and record their IDs so we can
+        # update them in-place without needing to know which ones have been
+        # created and in which order.
+        cls.limit_ids = {}
+
+    @classmethod
+    def _create_limit(cls, name, value):
+        return cls.os_system_admin.identity_limits_client.create_limit(
+            CONF.identity.region, cls.nova_service_id,
+            cls.servers_client.tenant_id, name, value)['limits'][0]['id']
+
+    def _update_limit(self, name, value):
+        self.os_system_admin.identity_limits_client.update_limit(
+            self.limit_ids[name], value)
+
+
+@testtools.skipUnless(CONF.compute_feature_enabled.unified_limits,
+                      'Compute unified limits are not enabled')
+class ServersQuotaTest(ComputeProjectQuotaTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(ServersQuotaTest, cls).resource_setup()
+
+        try:
+            cls.limit_ids['servers'] = cls._create_limit(
+                'servers', 5)
+            cls.limit_ids['class:VCPU'] = cls._create_limit(
+                'class:VCPU', 10)
+            cls.limit_ids['class:MEMORY_MB'] = cls._create_limit(
+                'class:MEMORY_MB', 25 * 1024)
+            cls.limit_ids['class:DISK_GB'] = cls._create_limit(
+                'class:DISK_GB', 10)
+        except lib_exc.Forbidden:
+            raise cls.skipException('Target system is not configured with '
+                                    'compute unified limits')
+
+    @decorators.idempotent_id('555d8bbf-d2ed-4e39-858c-4235899402d9')
+    @utils.services('compute')
+    def test_server_count_vcpu_memory_disk_quota(self):
+        # Set a quota on the number of servers for our tenant to one.
+        self._update_limit('servers', 1)
+
+        # Create one server.
+        first = self.create_server(name='first')
+
+        # Second server would put us over quota, so expect failure.
+        # NOTE: In nova, quota exceeded raises 403 Forbidden.
+        self.assertRaises(lib_exc.Forbidden,
+                          self.create_server,
+                          name='second')
+
+        # Update our limit to two.
+        self._update_limit('servers', 2)
+
+        # Now the same create should succeed.
+        second = self.create_server(name='second')
+
+        # Third server would put us over quota, so expect failure.
+        self.assertRaises(lib_exc.Forbidden,
+                          self.create_server,
+                          name='third')
+
+        # Delete the first server to put us under quota.
+        self.servers_client.delete_server(first['id'])
+        waiters.wait_for_server_termination(self.servers_client, first['id'])
+
+        # Now the same create should succeed.
+        third = self.create_server(name='third')
+
+        # Set the servers limit back to 10 to test other resources.
+        self._update_limit('servers', 10)
+
+        # Default flavor has: VCPU=1, MEMORY_MB=512, DISK_GB=1
+        # We are currently using 2 VCPU, set the limit to 2.
+        self._update_limit('class:VCPU', 2)
+
+        # Server create should fail as it would go over quota.
+        self.assertRaises(lib_exc.Forbidden,
+                          self.create_server,
+                          name='fourth')
+
+        # Delete the second server to put us under quota.
+        self.servers_client.delete_server(second['id'])
+        waiters.wait_for_server_termination(self.servers_client, second['id'])
+
+        # Same create should now succeed.
+        fourth = self.create_server(name='fourth')
+
+        # We are currently using 2 DISK_GB. Set limit to 1.
+        self._update_limit('class:DISK_GB', 1)
+
+        # Server create should fail because we're already over (new) quota.
+        self.assertRaises(lib_exc.Forbidden,
+                          self.create_server,
+                          name='fifth')
+
+        # Delete the third server.
+        self.servers_client.delete_server(third['id'])
+        waiters.wait_for_server_termination(self.servers_client, third['id'])
+
+        # Server create should fail again because it would still put us over
+        # quota.
+        self.assertRaises(lib_exc.Forbidden,
+                          self.create_server,
+                          name='fifth')
+
+        # Delete the fourth server.
+        self.servers_client.delete_server(fourth['id'])
+        waiters.wait_for_server_termination(self.servers_client, fourth['id'])
+
+        # Server create should succeed now.
+        self.create_server(name='fifth')