Merge "Add quotas per share type"
diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py
index a2332be..8b27700 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.38",
+               default="2.39",
                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 510b2f7..3631c68 100644
--- a/manila_tempest_tests/services/share/v2/json/shares_client.py
+++ b/manila_tempest_tests/services/share/v2/json/shares_client.py
@@ -852,11 +852,23 @@
 
 ###############
 
-    def _get_quotas_url(self, version):
+    @staticmethod
+    def _get_quotas_url(version):
         if utils.is_microversion_gt(version, "2.6"):
             return 'quota-sets'
         return 'os-quota-sets'
 
+    @staticmethod
+    def _get_quotas_url_arguments_as_str(user_id=None, share_type=None):
+        args_str = ''
+        if not (user_id is None or share_type is None):
+            args_str = "?user_id=%s&share_type=%s" % (user_id, share_type)
+        elif user_id is not None:
+            args_str = "?user_id=%s" % user_id
+        elif share_type is not None:
+            args_str = "?share_type=%s" % share_type
+        return args_str
+
     def default_quotas(self, tenant_id, url=None, version=LATEST_MICROVERSION):
         if url is None:
             url = self._get_quotas_url(version)
@@ -865,48 +877,44 @@
         self.expected_success(200, resp.status)
         return self._parse_resp(body)
 
-    def show_quotas(self, tenant_id, user_id=None, url=None,
+    def show_quotas(self, tenant_id, user_id=None, share_type=None, url=None,
                     version=LATEST_MICROVERSION):
         if url is None:
             url = self._get_quotas_url(version)
         url += '/%s' % tenant_id
-        if user_id is not None:
-            url += "?user_id=%s" % user_id
+        url += self._get_quotas_url_arguments_as_str(user_id, share_type)
         resp, body = self.get(url, version=version)
         self.expected_success(200, resp.status)
         return self._parse_resp(body)
 
-    def reset_quotas(self, tenant_id, user_id=None, url=None,
+    def reset_quotas(self, tenant_id, user_id=None, share_type=None, url=None,
                      version=LATEST_MICROVERSION):
         if url is None:
             url = self._get_quotas_url(version)
         url += '/%s' % tenant_id
-        if user_id is not None:
-            url += "?user_id=%s" % user_id
+        url += self._get_quotas_url_arguments_as_str(user_id, share_type)
         resp, body = self.delete(url, version=version)
         self.expected_success(202, resp.status)
         return body
 
-    def detail_quotas(self, tenant_id, user_id=None, url=None,
+    def detail_quotas(self, tenant_id, user_id=None, share_type=None, url=None,
                       version=LATEST_MICROVERSION):
         if url is None:
             url = self._get_quotas_url(version)
         url += '/%s/detail' % tenant_id
-        if user_id is not None:
-            url += "?user_id=%s" % user_id
+        url += self._get_quotas_url_arguments_as_str(user_id, share_type)
         resp, body = self.get(url, version=version)
         self.expected_success(200, resp.status)
         return self._parse_resp(body)
 
     def update_quotas(self, tenant_id, user_id=None, shares=None,
                       snapshots=None, gigabytes=None, snapshot_gigabytes=None,
-                      share_networks=None, force=True, url=None,
-                      version=LATEST_MICROVERSION):
+                      share_networks=None, force=True, share_type=None,
+                      url=None, version=LATEST_MICROVERSION):
         if url is None:
             url = self._get_quotas_url(version)
         url += '/%s' % tenant_id
-        if user_id is not None:
-            url += "?user_id=%s" % user_id
+        url += self._get_quotas_url_arguments_as_str(user_id, share_type)
 
         put_body = {"tenant_id": tenant_id}
         if force:
diff --git a/manila_tempest_tests/tests/api/admin/test_quotas.py b/manila_tempest_tests/tests/api/admin/test_quotas.py
index fec32f3..741a2bf 100644
--- a/manila_tempest_tests/tests/api/admin/test_quotas.py
+++ b/manila_tempest_tests/tests/api/admin/test_quotas.py
@@ -13,7 +13,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import ddt
 from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
 from testtools import testcase as tc
 
 from manila_tempest_tests.tests.api import base
@@ -21,6 +24,7 @@
 CONF = config.CONF
 
 
+@ddt.ddt
 class SharesAdminQuotasTest(base.BaseSharesAdminTest):
 
     @classmethod
@@ -60,7 +64,37 @@
         self.assertGreater(int(quotas["snapshots"]), -2)
         self.assertGreater(int(quotas["share_networks"]), -2)
 
+    @ddt.data(
+        ('id', True),
+        ('name', False),
+    )
+    @ddt.unpack
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API)
+    @base.skip_if_microversion_lt("2.39")
+    def test_show_share_type_quotas(self, share_type_key, is_st_public):
+        # Create share type
+        share_type = self.create_share_type(
+            data_utils.rand_name("tempest-manila"),
+            is_public=is_st_public,
+            cleanup_in_class=False,
+            extra_specs=self.add_extra_specs_to_dict(),
+        )
+        if 'share_type' in share_type:
+            share_type = share_type['share_type']
 
+        # Get current project quotas
+        p_quotas = self.shares_v2_client.show_quotas(self.tenant_id)
+
+        # Get current quotas
+        st_quotas = self.shares_v2_client.show_quotas(
+            self.tenant_id, share_type=share_type[share_type_key])
+
+        # Share type quotas have values equal to project's
+        for key in ('shares', 'gigabytes', 'snapshots', 'snapshot_gigabytes'):
+            self.assertEqual(st_quotas[key], p_quotas[key])
+
+
+@ddt.ddt
 class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
 
     force_tenant_isolation = True
@@ -101,6 +135,47 @@
             self.tenant_id, self.user_id, shares=new_quota)
         self.assertEqual(new_quota, int(updated["shares"]))
 
+    def _create_share_type(self):
+        share_type = self.create_share_type(
+            data_utils.rand_name("tempest-manila"),
+            cleanup_in_class=False,
+            client=self.shares_v2_client,
+            extra_specs=self.add_extra_specs_to_dict(),
+        )
+        if 'share_type' in share_type:
+            share_type = share_type['share_type']
+        return share_type
+
+    @ddt.data(
+        ('id', True),
+        ('name', False),
+    )
+    @ddt.unpack
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API)
+    @base.skip_if_microversion_lt("2.39")
+    def test_update_share_type_quota(self, share_type_key, is_st_public):
+        share_type = self._create_share_type()
+
+        # Get current quotas
+        quotas = self.client.show_quotas(
+            self.tenant_id, share_type=share_type[share_type_key])
+
+        # Update quotas
+        for q in ('shares', 'gigabytes', 'snapshots', 'snapshot_gigabytes'):
+            new_quota = int(quotas[q]) - 1
+
+            # Set new quota
+            updated = self.client.update_quotas(
+                self.tenant_id, share_type=share_type[share_type_key],
+                **{q: new_quota})
+            self.assertEqual(new_quota, int(updated[q]))
+
+        current_quotas = self.client.show_quotas(
+            self.tenant_id, share_type=share_type[share_type_key])
+
+        for q in ('shares', 'gigabytes', 'snapshots', 'snapshot_gigabytes'):
+            self.assertEqual(int(quotas[q]) - 1, current_quotas[q])
+
     @tc.attr(base.TAG_POSITIVE, base.TAG_API)
     def test_update_tenant_quota_snapshots(self):
         # get current quotas
@@ -244,6 +319,51 @@
         self.assertEqual(int(default["share_networks"]),
                          int(reseted["share_networks"]))
 
+    @ddt.data(
+        ('id', True),
+        ('name', False),
+    )
+    @ddt.unpack
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API)
+    @base.skip_if_microversion_lt("2.39")
+    def test_reset_share_type_quotas(self, share_type_key, is_st_public):
+        share_type = self._create_share_type()
+
+        # get default_quotas
+        default_quotas = self.client.default_quotas(self.tenant_id)
+
+        # set new quota for project
+        updated_p_quota = self.client.update_quotas(
+            self.tenant_id,
+            shares=int(default_quotas['shares']) + 5,
+            snapshots=int(default_quotas['snapshots']) + 5,
+            gigabytes=int(default_quotas['gigabytes']) + 5,
+            snapshot_gigabytes=int(default_quotas['snapshot_gigabytes']) + 5)
+
+        # set new quota for project
+        self.client.update_quotas(
+            self.tenant_id,
+            share_type=share_type[share_type_key],
+            shares=int(default_quotas['shares']) + 3,
+            snapshots=int(default_quotas['snapshots']) + 3,
+            gigabytes=int(default_quotas['gigabytes']) + 3,
+            snapshot_gigabytes=int(default_quotas['snapshot_gigabytes']) + 3)
+
+        # reset share type quotas
+        self.client.reset_quotas(
+            self.tenant_id, share_type=share_type[share_type_key])
+
+        # verify quotas
+        current_p_quota = self.client.show_quotas(self.tenant_id)
+        current_st_quota = self.client.show_quotas(
+            self.tenant_id, share_type=share_type[share_type_key])
+        for key in ('shares', 'snapshots', 'gigabytes', 'snapshot_gigabytes'):
+            self.assertEqual(updated_p_quota[key], current_p_quota[key])
+
+            # Default share type quotas are current project quotas
+            self.assertNotEqual(default_quotas[key], current_st_quota[key])
+            self.assertEqual(current_p_quota[key], current_st_quota[key])
+
     @tc.attr(base.TAG_POSITIVE, base.TAG_API)
     def test_unlimited_quota_for_shares(self):
         self.client.update_quotas(self.tenant_id, shares=-1)
@@ -329,3 +449,95 @@
         quotas = self.client.show_quotas(self.tenant_id, self.user_id)
 
         self.assertEqual(-1, quotas.get('share_networks'))
+
+    @ddt.data(11, -1)
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    def test_update_user_quotas_bigger_than_project_quota(self, user_quota):
+        self.client.update_quotas(self.tenant_id, shares=10)
+        self.client.update_quotas(
+            self.tenant_id, user_id=self.user_id, force=True,
+            shares=user_quota)
+
+    @ddt.data(11, -1)
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @base.skip_if_microversion_lt("2.39")
+    def test_update_share_type_quotas_bigger_than_project_quota(self, st_q):
+        share_type = self._create_share_type()
+        self.client.update_quotas(self.tenant_id, shares=10)
+
+        self.client.update_quotas(
+            self.tenant_id, share_type=share_type['name'], force=True,
+            shares=st_q)
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @base.skip_if_microversion_lt("2.39")
+    def test_set_share_type_quota_bigger_than_users_quota(self):
+        share_type = self._create_share_type()
+        self.client.update_quotas(self.tenant_id, force=False, shares=13)
+        self.client.update_quotas(
+            self.tenant_id, user_id=self.user_id, force=False, shares=11)
+
+        # Share type quota does not depend on user's quota, so we should be
+        # able to update it.
+        self.client.update_quotas(
+            self.tenant_id, share_type=share_type['name'], force=False,
+            shares=12)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @base.skip_if_microversion_lt("2.39")
+    def test_quotas_usages(self):
+        # Create share types
+        st_1, st_2 = (self._create_share_type() for i in (1, 2))
+
+        # Set quotas for project, user and both share types
+        self.client.update_quotas(self.tenant_id, shares=3, gigabytes=10)
+        self.client.update_quotas(
+            self.tenant_id, user_id=self.user_id, shares=2, gigabytes=7)
+        for st in (st_1['id'], st_2['name']):
+            self.client.update_quotas(
+                self.tenant_id, share_type=st, shares=2, gigabytes=4)
+
+        # Create share, 4Gb, st1 - ok
+        share_1 = self.create_share(
+            size=4, share_type_id=st_1['id'], client=self.client,
+            cleanup_in_class=False)
+
+        # Try create shares twice, failing on user and share type quotas
+        for size, st_id in ((3, st_1['id']), (4, st_2['id'])):
+            self.assertRaises(
+                lib_exc.OverLimit,
+                self.create_share,
+                size=size, share_type_id=st_id, client=self.client,
+                cleanup_in_class=False)
+
+        # Create share, 3Gb, st2 - ok
+        share_2 = self.create_share(
+            size=3, share_type_id=st_2['id'], client=self.client,
+            cleanup_in_class=False)
+
+        # Check quota usages
+        for g_l, g_use, s_l, s_use, kwargs in (
+                (10, 7, 3, 2, {}),
+                (7, 7, 2, 2, {'user_id': self.user_id}),
+                (4, 4, 2, 1, {'share_type': st_1['id']}),
+                (4, 3, 2, 1, {'share_type': st_2['name']})):
+            quotas = self.client.detail_quotas(
+                tenant_id=self.tenant_id, **kwargs)
+            self.assertEqual(0, quotas['gigabytes']['reserved'])
+            self.assertEqual(g_l, quotas['gigabytes']['limit'])
+            self.assertEqual(g_use, quotas['gigabytes']['in_use'])
+            self.assertEqual(0, quotas['shares']['reserved'])
+            self.assertEqual(s_l, quotas['shares']['limit'])
+            self.assertEqual(s_use, quotas['shares']['in_use'])
+
+        # Delete shares and then check usages
+        for share_id in (share_1['id'], share_2['id']):
+            self.client.delete_share(share_id)
+            self.client.wait_for_resource_deletion(share_id=share_id)
+        for kwargs in ({}, {'share_type': st_1['name']},
+                       {'user_id': self.user_id}, {'share_type': st_2['id']}):
+            quotas = self.client.detail_quotas(
+                tenant_id=self.tenant_id, **kwargs)
+            for key in ('shares', 'gigabytes'):
+                self.assertEqual(0, quotas[key]['reserved'])
+                self.assertEqual(0, quotas[key]['in_use'])
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 b8557e1..fe4f562 100644
--- a/manila_tempest_tests/tests/api/admin/test_quotas_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_quotas_negative.py
@@ -15,6 +15,7 @@
 
 import ddt
 from tempest import config
+from tempest.lib.common.utils import data_utils
 from tempest.lib import exceptions as lib_exc
 from testtools import testcase as tc
 
@@ -117,6 +118,7 @@
                           client.update_quotas,
                           client.tenant_id,
                           client.user_id,
+                          force=False,
                           shares=bigger_value)
 
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
@@ -132,6 +134,7 @@
                           client.update_quotas,
                           client.tenant_id,
                           client.user_id,
+                          force=False,
                           snapshots=bigger_value)
 
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
@@ -147,6 +150,7 @@
                           client.update_quotas,
                           client.tenant_id,
                           client.user_id,
+                          force=False,
                           gigabytes=bigger_value)
 
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
@@ -162,6 +166,7 @@
                           client.update_quotas,
                           client.tenant_id,
                           client.user_id,
+                          force=False,
                           snapshot_gigabytes=bigger_value)
 
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
@@ -177,6 +182,7 @@
                           client.update_quotas,
                           client.tenant_id,
                           client.user_id,
+                          force=False,
                           share_networks=bigger_value)
 
     @ddt.data(
@@ -215,3 +221,98 @@
             self.shares_v2_client.tenant_id,
             version=version, url=url,
         )
+
+    @ddt.data('show', 'reset', 'update')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @base.skip_if_microversion_lt("2.39")
+    def test_share_type_quotas_using_nonexistent_share_type(self, op):
+        client = self.get_client_with_isolated_creds(client_version='2')
+
+        kwargs = {"share_type": "fake_nonexistent_share_type"}
+        if op == 'update':
+            tenant_quotas = client.show_quotas(client.tenant_id)
+            kwargs['shares'] = tenant_quotas['shares']
+
+        self.assertRaises(
+            lib_exc.NotFound,
+            getattr(client, op + '_quotas'),
+            client.tenant_id,
+            **kwargs)
+
+    def _create_share_type(self):
+        share_type = self.create_share_type(
+            data_utils.rand_name("tempest-manila"),
+            cleanup_in_class=False,
+            client=self.shares_v2_client,
+            extra_specs=self.add_extra_specs_to_dict(),
+        )
+        if 'share_type' in share_type:
+            share_type = share_type['share_type']
+        return share_type
+
+    @ddt.data('id', 'name')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @base.skip_if_microversion_lt("2.39")
+    def test_try_update_share_type_quota_for_share_networks(self, key):
+        client = self.get_client_with_isolated_creds(client_version='2')
+        share_type = self._create_share_type()
+        tenant_quotas = client.show_quotas(client.tenant_id)
+
+        # Try to set 'share_networks' quota for share type
+        self.assertRaises(
+            lib_exc.BadRequest,
+            client.update_quotas,
+            client.tenant_id,
+            share_type=share_type[key],
+            share_networks=int(tenant_quotas["share_networks"]),
+        )
+
+    @ddt.data('show', 'reset', 'update')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @base.skip_if_microversion_lt("2.38")
+    def test_share_type_quotas_using_too_old_microversion(self, op):
+        client = self.get_client_with_isolated_creds(client_version='2')
+        share_type = self._create_share_type()
+        kwargs = {"version": "2.38", "share_type": share_type["name"]}
+        if op == 'update':
+            tenant_quotas = client.show_quotas(client.tenant_id)
+            kwargs['shares'] = tenant_quotas['shares']
+
+        self.assertRaises(
+            lib_exc.BadRequest,
+            getattr(client, op + '_quotas'),
+            client.tenant_id,
+            **kwargs)
+
+    @ddt.data('show', 'reset', 'update')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @base.skip_if_microversion_lt("2.39")
+    def test_quotas_providing_share_type_and_user_id(self, op):
+        client = self.get_client_with_isolated_creds(client_version='2')
+        share_type = self._create_share_type()
+        kwargs = {"share_type": share_type["name"], "user_id": client.user_id}
+        if op == 'update':
+            tenant_quotas = client.show_quotas(client.tenant_id)
+            kwargs['shares'] = tenant_quotas['shares']
+
+        self.assertRaises(
+            lib_exc.BadRequest,
+            getattr(client, op + '_quotas'),
+            client.tenant_id,
+            **kwargs)
+
+    @ddt.data(11, -1)
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    @base.skip_if_microversion_lt("2.39")
+    def test_update_share_type_quotas_bigger_than_project_quota(self, st_q):
+        client = self.get_client_with_isolated_creds(client_version='2')
+        share_type = self._create_share_type()
+        client.update_quotas(client.tenant_id, shares=10)
+
+        self.assertRaises(
+            lib_exc.BadRequest,
+            client.update_quotas,
+            client.tenant_id,
+            share_type=share_type['name'],
+            force=False,
+            shares=st_q)