Merge "Add share groups and share group snapshots quotas"
diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py
index 8b27700..9e4a168 100644
--- a/manila_tempest_tests/config.py
+++ b/manila_tempest_tests/config.py
@@ -30,7 +30,7 @@
                help="The minimum api microversion is configured to be the "
                     "value of the minimum microversion supported by Manila."),
     cfg.StrOpt("max_api_microversion",
-               default="2.39",
+               default="2.40",
                help="The maximum api microversion is configured to be the "
                     "value of the latest microversion supported by Manila."),
     cfg.StrOpt("region",
diff --git a/manila_tempest_tests/services/share/v2/json/shares_client.py b/manila_tempest_tests/services/share/v2/json/shares_client.py
index 3631c68..94e1ac6 100644
--- a/manila_tempest_tests/services/share/v2/json/shares_client.py
+++ b/manila_tempest_tests/services/share/v2/json/shares_client.py
@@ -909,7 +909,9 @@
 
     def update_quotas(self, tenant_id, user_id=None, shares=None,
                       snapshots=None, gigabytes=None, snapshot_gigabytes=None,
-                      share_networks=None, force=True, share_type=None,
+                      share_networks=None,
+                      share_groups=None, share_group_snapshots=None,
+                      force=True, share_type=None,
                       url=None, version=LATEST_MICROVERSION):
         if url is None:
             url = self._get_quotas_url(version)
@@ -929,6 +931,10 @@
             put_body["snapshot_gigabytes"] = snapshot_gigabytes
         if share_networks is not None:
             put_body["share_networks"] = share_networks
+        if share_groups is not None:
+            put_body["share_groups"] = share_groups
+        if share_group_snapshots is not None:
+            put_body["share_group_snapshots"] = share_group_snapshots
         put_body = json.dumps({"quota_set": put_body})
 
         resp, body = self.put(url, put_body, version=version)
diff --git a/manila_tempest_tests/tests/api/admin/test_quotas.py b/manila_tempest_tests/tests/api/admin/test_quotas.py
index 741a2bf..02d0a62 100644
--- a/manila_tempest_tests/tests/api/admin/test_quotas.py
+++ b/manila_tempest_tests/tests/api/admin/test_quotas.py
@@ -17,11 +17,15 @@
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import exceptions as lib_exc
+import testtools
 from testtools import testcase as tc
 
 from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
 
 CONF = config.CONF
+PRE_SHARE_GROUPS_MICROVERSION = "2.39"
+SHARE_GROUPS_MICROVERSION = "2.40"
 
 
 @ddt.ddt
@@ -44,6 +48,9 @@
         self.assertGreater(int(quotas["shares"]), -2)
         self.assertGreater(int(quotas["snapshots"]), -2)
         self.assertGreater(int(quotas["share_networks"]), -2)
+        if utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION):
+            self.assertGreater(int(quotas["share_groups"]), -2)
+            self.assertGreater(int(quotas["share_group_snapshots"]), -2)
 
     @tc.attr(base.TAG_POSITIVE, base.TAG_API)
     def test_show_quotas(self):
@@ -53,6 +60,9 @@
         self.assertGreater(int(quotas["shares"]), -2)
         self.assertGreater(int(quotas["snapshots"]), -2)
         self.assertGreater(int(quotas["share_networks"]), -2)
+        if utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION):
+            self.assertGreater(int(quotas["share_groups"]), -2)
+            self.assertGreater(int(quotas["share_group_snapshots"]), -2)
 
     @tc.attr(base.TAG_POSITIVE, base.TAG_API)
     def test_show_quotas_for_user(self):
@@ -63,6 +73,28 @@
         self.assertGreater(int(quotas["shares"]), -2)
         self.assertGreater(int(quotas["snapshots"]), -2)
         self.assertGreater(int(quotas["share_networks"]), -2)
+        if utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION):
+            self.assertGreater(int(quotas["share_groups"]), -2)
+            self.assertGreater(int(quotas["share_group_snapshots"]), -2)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API)
+    @base.skip_if_microversion_not_supported(PRE_SHARE_GROUPS_MICROVERSION)
+    def test_show_sg_quotas_using_too_old_microversion(self):
+        quotas = self.shares_v2_client.show_quotas(
+            self.tenant_id, version=PRE_SHARE_GROUPS_MICROVERSION)
+
+        for key in ('share_groups', 'share_group_snapshots'):
+            self.assertNotIn(key, quotas)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API)
+    @base.skip_if_microversion_not_supported(PRE_SHARE_GROUPS_MICROVERSION)
+    def test_show_sg_quotas_for_user_using_too_old_microversion(self):
+        quotas = self.shares_v2_client.show_quotas(
+            self.tenant_id, self.user_id,
+            version=PRE_SHARE_GROUPS_MICROVERSION)
+
+        for key in ('share_groups', 'share_group_snapshots'):
+            self.assertNotIn(key, quotas)
 
     @ddt.data(
         ('id', True),
@@ -93,12 +125,16 @@
         for key in ('shares', 'gigabytes', 'snapshots', 'snapshot_gigabytes'):
             self.assertEqual(st_quotas[key], p_quotas[key])
 
+        # Verify that we do not have share groups related quotas
+        # for share types.
+        for key in ('share_groups', 'share_group_snapshots'):
+            self.assertNotIn(key, st_quotas)
+
 
 @ddt.ddt
 class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
 
     force_tenant_isolation = True
-    client_version = '2'
 
     @classmethod
     def resource_setup(cls):
@@ -109,8 +145,7 @@
 
     def setUp(self):
         super(self.__class__, self).setUp()
-        self.client = self.get_client_with_isolated_creds(
-            client_version=self.client_version)
+        self.client = self.get_client_with_isolated_creds(client_version='2')
         self.tenant_id = self.client.tenant_id
         self.user_id = self.client.user_id
 
@@ -124,6 +159,24 @@
         updated = self.client.update_quotas(self.tenant_id, shares=new_quota)
         self.assertEqual(new_quota, int(updated["shares"]))
 
+    @ddt.data(
+        "share_groups",
+        "share_group_snapshots",
+    )
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API)
+    @testtools.skipUnless(
+        CONF.share.run_share_group_tests, 'Share Group tests disabled.')
+    @utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
+    def test_update_tenant_quota_share_groups(self, quota_key):
+        # Get current quotas
+        quotas = self.client.show_quotas(self.tenant_id)
+        new_quota = int(quotas[quota_key]) + 2
+
+        # Set new quota
+        updated = self.client.update_quotas(
+            self.tenant_id, **{quota_key: new_quota})
+        self.assertEqual(new_quota, int(updated[quota_key]))
+
     @tc.attr(base.TAG_POSITIVE, base.TAG_API)
     def test_update_user_quota_shares(self):
         # get current quotas
@@ -135,6 +188,24 @@
             self.tenant_id, self.user_id, shares=new_quota)
         self.assertEqual(new_quota, int(updated["shares"]))
 
+    @ddt.data(
+        "share_groups",
+        "share_group_snapshots",
+    )
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API)
+    @testtools.skipUnless(
+        CONF.share.run_share_group_tests, 'Share Group tests disabled.')
+    @utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
+    def test_update_user_quota_share_groups(self, quota_key):
+        # Get current quotas
+        quotas = self.client.show_quotas(self.tenant_id, self.user_id)
+        new_quota = int(quotas[quota_key]) - 1
+
+        # Set new quota
+        updated = self.client.update_quotas(
+            self.tenant_id, self.user_id, **{quota_key: new_quota})
+        self.assertEqual(new_quota, int(updated[quota_key]))
+
     def _create_share_type(self):
         share_type = self.create_share_type(
             data_utils.rand_name("tempest-manila"),
@@ -280,44 +351,63 @@
 
     @tc.attr(base.TAG_POSITIVE, base.TAG_API)
     def test_reset_tenant_quotas(self):
-        # get default_quotas
+        # Get default_quotas
         default = self.client.default_quotas(self.tenant_id)
 
-        # get current quotas
+        # Get current quotas
         custom = self.client.show_quotas(self.tenant_id)
 
-        # make quotas for update
-        shares = int(custom["shares"]) + 2
-        snapshots = int(custom["snapshots"]) + 2
-        gigabytes = int(custom["gigabytes"]) + 2
-        snapshot_gigabytes = int(custom["snapshot_gigabytes"]) + 2
-        share_networks = int(custom["share_networks"]) + 2
+        # Make quotas for update
+        data = {
+            "shares": int(custom["shares"]) + 2,
+            "snapshots": int(custom["snapshots"]) + 2,
+            "gigabytes": int(custom["gigabytes"]) + 2,
+            "snapshot_gigabytes": int(custom["snapshot_gigabytes"]) + 2,
+            "share_networks": int(custom["share_networks"]) + 2,
+        }
+        if (utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION) and
+                CONF.share.run_share_group_tests):
+            data["share_groups"] = int(custom["share_groups"]) + 2
+            data["share_group_snapshots"] = (
+                int(custom["share_group_snapshots"]) + 2)
 
         # set new quota
-        updated = self.client.update_quotas(
-            self.tenant_id,
-            shares=shares,
-            snapshots=snapshots,
-            gigabytes=gigabytes,
-            snapshot_gigabytes=snapshot_gigabytes,
-            share_networks=share_networks)
-        self.assertEqual(shares, int(updated["shares"]))
-        self.assertEqual(snapshots, int(updated["snapshots"]))
-        self.assertEqual(gigabytes, int(updated["gigabytes"]))
-        self.assertEqual(snapshot_gigabytes,
-                         int(updated["snapshot_gigabytes"]))
-        self.assertEqual(share_networks, int(updated["share_networks"]))
+        updated = self.client.update_quotas(self.tenant_id, **data)
+        self.assertEqual(data["shares"], int(updated["shares"]))
+        self.assertEqual(data["snapshots"], int(updated["snapshots"]))
+        self.assertEqual(data["gigabytes"], int(updated["gigabytes"]))
+        self.assertEqual(
+            data["snapshot_gigabytes"], int(updated["snapshot_gigabytes"]))
+        self.assertEqual(
+            data["share_networks"], int(updated["share_networks"]))
+        if (utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION) and
+                CONF.share.run_share_group_tests):
+            self.assertEqual(
+                data["share_groups"], int(updated["share_groups"]))
+            self.assertEqual(
+                data["share_group_snapshots"],
+                int(updated["share_group_snapshots"]))
 
-        # reset customized quotas
+        # Reset customized quotas
         self.client.reset_quotas(self.tenant_id)
 
-        # verify quotas
+        # Verify quotas
         reseted = self.client.show_quotas(self.tenant_id)
         self.assertEqual(int(default["shares"]), int(reseted["shares"]))
         self.assertEqual(int(default["snapshots"]), int(reseted["snapshots"]))
         self.assertEqual(int(default["gigabytes"]), int(reseted["gigabytes"]))
-        self.assertEqual(int(default["share_networks"]),
-                         int(reseted["share_networks"]))
+        self.assertEqual(
+            int(default["snapshot_gigabytes"]),
+            int(reseted["snapshot_gigabytes"]))
+        self.assertEqual(
+            int(default["share_networks"]), int(reseted["share_networks"]))
+        if (utils.is_microversion_supported(SHARE_GROUPS_MICROVERSION) and
+                CONF.share.run_share_group_tests):
+            self.assertEqual(
+                int(default["share_groups"]), int(reseted["share_groups"]))
+            self.assertEqual(
+                int(default["share_group_snapshots"]),
+                int(reseted["share_group_snapshots"]))
 
     @ddt.data(
         ('id', True),
@@ -450,6 +540,29 @@
 
         self.assertEqual(-1, quotas.get('share_networks'))
 
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API)
+    @testtools.skipUnless(
+        CONF.share.run_share_group_tests, 'Share Group tests disabled.')
+    @utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
+    def test_unlimited_quota_for_share_groups(self):
+        self.client.update_quotas(self.tenant_id, share_groups=-1)
+
+        quotas = self.client.show_quotas(self.tenant_id)
+
+        self.assertEqual(-1, quotas.get('share_groups'))
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API)
+    @testtools.skipUnless(
+        CONF.share.run_share_group_tests, 'Share Group tests disabled.')
+    @utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
+    def test_unlimited_user_quota_for_share_group_snapshots(self):
+        self.client.update_quotas(
+            self.tenant_id, self.user_id, share_group_snapshots=-1)
+
+        quotas = self.client.show_quotas(self.tenant_id, self.user_id)
+
+        self.assertEqual(-1, quotas.get('share_group_snapshots'))
+
     @ddt.data(11, -1)
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
     def test_update_user_quotas_bigger_than_project_quota(self, user_quota):
@@ -541,3 +654,82 @@
             for key in ('shares', 'gigabytes'):
                 self.assertEqual(0, quotas[key]['reserved'])
                 self.assertEqual(0, quotas[key]['in_use'])
+
+    def _check_sg_usages(self, quotas, in_use, limit):
+        """Helper method for 'test_share_group_quotas_usages' test."""
+        self.assertEqual(0, int(quotas['share_groups']['reserved']))
+        self.assertEqual(in_use, int(quotas['share_groups']['in_use']))
+        self.assertEqual(limit, int(quotas['share_groups']['limit']))
+
+    def _check_sgs_usages(self, quotas, in_use):
+        """Helper method for 'test_share_group_quotas_usages' test."""
+        self.assertEqual(0, int(quotas['share_group_snapshots']['reserved']))
+        self.assertEqual(
+            in_use, int(quotas['share_group_snapshots']['in_use']))
+        self.assertEqual(1, int(quotas['share_group_snapshots']['limit']))
+
+    def _check_usages(self, sg_in_use, sgs_in_use):
+        """Helper method for 'test_share_group_quotas_usages' test."""
+        p_quotas = self.client.detail_quotas(tenant_id=self.tenant_id)
+        u_quotas = self.client.detail_quotas(
+            tenant_id=self.tenant_id, user_id=self.user_id)
+        self._check_sg_usages(p_quotas, sg_in_use, 3)
+        self._check_sg_usages(u_quotas, sg_in_use, 2)
+        self._check_sgs_usages(p_quotas, sgs_in_use)
+        self._check_sgs_usages(u_quotas, sgs_in_use)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @testtools.skipUnless(
+        CONF.share.run_share_group_tests, 'Share Group tests disabled.')
+    @base.skip_if_microversion_lt(SHARE_GROUPS_MICROVERSION)
+    def test_share_group_quotas_usages(self):
+        # Set quotas for project (3 SG, 1 SGS) and user (2 SG, 1 SGS)
+        self.client.update_quotas(
+            self.tenant_id, share_groups=3, share_group_snapshots=1)
+        self.client.update_quotas(
+            self.tenant_id, user_id=self.user_id,
+            share_groups=2, share_group_snapshots=1)
+
+        # Check usages, they should be 0s
+        self._check_usages(0, 0)
+
+        # Create SG1 and check usages
+        share_group1 = self.create_share_group(
+            cleanup_in_class=False, client=self.client)
+        self._check_usages(1, 0)
+
+        # Create SGS1 and check usages
+        sg_snapshot = self.create_share_group_snapshot_wait_for_active(
+            share_group1['id'], cleanup_in_class=False, client=self.client)
+        self._check_usages(1, 1)
+
+        # Create SG2 from SGS1 and check usages
+        share_group2 = self.create_share_group(
+            cleanup_in_class=False, client=self.client,
+            source_share_group_snapshot_id=sg_snapshot['id'])
+        self._check_usages(2, 1)
+
+        # Try create SGS2, fail, then check usages
+        self.assertRaises(
+            lib_exc.OverLimit,
+            self.create_share_group,
+            client=self.client, cleanup_in_class=False)
+        self._check_usages(2, 1)
+
+        # Delete SG2 and check usages
+        self.client.delete_share_group(share_group2['id'])
+        self.client.wait_for_resource_deletion(
+            share_group_id=share_group2['id'])
+        self._check_usages(1, 1)
+
+        # Delete SGS1 and check usages
+        self.client.delete_share_group_snapshot(sg_snapshot['id'])
+        self.client.wait_for_resource_deletion(
+            share_group_snapshot_id=sg_snapshot['id'])
+        self._check_usages(1, 0)
+
+        # Delete SG1 and check usages
+        self.client.delete_share_group(share_group1['id'])
+        self.client.wait_for_resource_deletion(
+            share_group_id=share_group1['id'])
+        self._check_usages(0, 0)
diff --git a/manila_tempest_tests/tests/api/admin/test_quotas_negative.py b/manila_tempest_tests/tests/api/admin/test_quotas_negative.py
index fe4f562..91b2507 100644
--- a/manila_tempest_tests/tests/api/admin/test_quotas_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_quotas_negative.py
@@ -17,11 +17,15 @@
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import exceptions as lib_exc
+import testtools
 from testtools import testcase as tc
 
 from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
 
 CONF = config.CONF
+PRE_SHARE_GROUPS_MICROVERSION = "2.39"
+SHARE_GROUPS_MICROVERSION = "2.40"
 
 
 @ddt.ddt
@@ -49,50 +53,35 @@
         self.assertRaises(lib_exc.NotFound,
                           client.reset_quotas, "")
 
+    @ddt.data(
+        {"shares": -2},
+        {"snapshots": -2},
+        {"gigabytes": -2},
+        {"snapshot_gigabytes": -2},
+        {"share_networks": -2},
+    )
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
-    def test_update_shares_quota_with_wrong_data(self):
+    def test_update_quota_with_wrong_data(self, kwargs):
         # -1 is acceptable value as unlimited
         client = self.get_client_with_isolated_creds()
-        self.assertRaises(lib_exc.BadRequest,
-                          client.update_quotas,
-                          client.tenant_id,
-                          shares=-2)
+        self.assertRaises(
+            lib_exc.BadRequest,
+            client.update_quotas, client.tenant_id, **kwargs)
 
+    @ddt.data(
+        {"share_groups": -2},
+        {"share_group_snapshots": -2},
+    )
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
-    def test_update_snapshots_quota_with_wrong_data(self):
+    @testtools.skipUnless(
+        CONF.share.run_share_group_tests, 'Share Group tests disabled.')
+    @utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
+    def test_update_sg_quota_with_wrong_data(self, kwargs):
         # -1 is acceptable value as unlimited
-        client = self.get_client_with_isolated_creds()
-        self.assertRaises(lib_exc.BadRequest,
-                          client.update_quotas,
-                          client.tenant_id,
-                          snapshots=-2)
-
-    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
-    def test_update_gigabytes_quota_with_wrong_data(self):
-        # -1 is acceptable value as unlimited
-        client = self.get_client_with_isolated_creds()
-        self.assertRaises(lib_exc.BadRequest,
-                          client.update_quotas,
-                          client.tenant_id,
-                          gigabytes=-2)
-
-    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
-    def test_update_snapshot_gigabytes_quota_with_wrong_data(self):
-        # -1 is acceptable value as unlimited
-        client = self.get_client_with_isolated_creds()
-        self.assertRaises(lib_exc.BadRequest,
-                          client.update_quotas,
-                          client.tenant_id,
-                          snapshot_gigabytes=-2)
-
-    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
-    def test_update_share_networks_quota_with_wrong_data(self):
-        # -1 is acceptable value as unlimited
-        client = self.get_client_with_isolated_creds()
-        self.assertRaises(lib_exc.BadRequest,
-                          client.update_quotas,
-                          client.tenant_id,
-                          share_networks=-2)
+        client = self.get_client_with_isolated_creds(client_version='2')
+        self.assertRaises(
+            lib_exc.BadRequest,
+            client.update_quotas, client.tenant_id, **kwargs)
 
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
     def test_create_share_with_size_bigger_than_quota(self):
@@ -106,6 +95,21 @@
                           size=overquota)
 
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @testtools.skipUnless(
+        CONF.share.run_share_group_tests, 'Share Group tests disabled.')
+    @utils.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
+    def test_create_share_group_with_exceeding_quota_limit(self):
+        client = self.get_client_with_isolated_creds(client_version='2')
+        client.update_quotas(client.tenant_id, share_groups=0)
+
+        # Try schedule share group creation
+        self.assertRaises(
+            lib_exc.OverLimit,
+            self.create_share_group,
+            client=client,
+            cleanup_in_class=False)
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
     def test_try_set_user_quota_shares_bigger_than_tenant_quota(self):
         client = self.get_client_with_isolated_creds()
 
@@ -267,6 +271,41 @@
             share_networks=int(tenant_quotas["share_networks"]),
         )
 
+    @ddt.data('share_groups', 'share_group_snapshots')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @base.skip_if_microversion_lt(SHARE_GROUPS_MICROVERSION)
+    def test_try_update_share_type_quota_for_share_groups(self, quota_name):
+        client = self.get_client_with_isolated_creds(client_version='2')
+        share_type = self._create_share_type()
+        tenant_quotas = client.show_quotas(client.tenant_id)
+
+        self.assertRaises(
+            lib_exc.BadRequest,
+            client.update_quotas,
+            client.tenant_id,
+            share_type=share_type["name"],
+            **{quota_name: int(tenant_quotas[quota_name])}
+        )
+
+    @ddt.data('share_groups', 'share_group_snapshots')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @base.skip_if_microversion_not_supported(PRE_SHARE_GROUPS_MICROVERSION)
+    @base.skip_if_microversion_not_supported(SHARE_GROUPS_MICROVERSION)
+    def test_share_group_quotas_using_too_old_microversion(self, quota_key):
+        client = self.get_client_with_isolated_creds(client_version='2')
+        tenant_quotas = client.show_quotas(
+            client.tenant_id, version=SHARE_GROUPS_MICROVERSION)
+        kwargs = {
+            "version": PRE_SHARE_GROUPS_MICROVERSION,
+            quota_key: tenant_quotas[quota_key],
+        }
+
+        self.assertRaises(
+            lib_exc.BadRequest,
+            client.update_quotas,
+            client.tenant_id,
+            **kwargs)
+
     @ddt.data('show', 'reset', 'update')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
     @base.skip_if_microversion_lt("2.38")