Add test cases for volume quota class

Now tempest has provided tests for tenant's quota in volume, but
lacks tests for volume quota class. This patch adds this support.

Including:

[1] Add volume v2 quota class client as library
[2] Add release note
[3] Add test cases: show & update volume 'default' quota class
[4] Add unit tests for volume v2 quota class client
[5] Fix for test_volume_quotas.py

Change-Id: I30bac79b986e6e90d43dcc6f9d247e74a314bf3c
diff --git a/releasenotes/notes/add-volume-quota-class-client-as-library-c4c2b22c36ff807e.yaml b/releasenotes/notes/add-volume-quota-class-client-as-library-c4c2b22c36ff807e.yaml
new file mode 100644
index 0000000..e6847eb
--- /dev/null
+++ b/releasenotes/notes/add-volume-quota-class-client-as-library-c4c2b22c36ff807e.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Define v2 quota_classes_client for the volume service as library
+    interfaces, allowing other projects to use this module as stable libraries
+    without maintenance changes.
+
+    * quota_classes_client(v2)
diff --git a/tempest/api/volume/admin/test_volume_quota_classes.py b/tempest/api/volume/admin/test_volume_quota_classes.py
new file mode 100644
index 0000000..016d87a
--- /dev/null
+++ b/tempest/api/volume/admin/test_volume_quota_classes.py
@@ -0,0 +1,89 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.
+
+from oslo_log import log as logging
+from testtools import matchers
+
+from tempest.api.volume import base
+from tempest.common import tempest_fixtures as fixtures
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+LOG = logging.getLogger(__name__)
+QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups',
+              'backup_gigabytes', 'per_volume_gigabytes']
+
+
+class VolumeQuotaClassesTest(base.BaseVolumeAdminTest):
+
+    def setUp(self):
+        # Note(jeremy.zhang): All test cases in this class need to externally
+        # lock on doing anything with default quota values.
+        self.useFixture(fixtures.LockFixture('volume_quotas'))
+        super(VolumeQuotaClassesTest, self).setUp()
+
+    def _restore_default_quotas(self, original_defaults):
+        LOG.debug("Restoring volume quota class defaults")
+        self.admin_quota_classes_client.update_quota_class_set(
+            'default', **original_defaults)
+
+    @decorators.idempotent_id('abb9198e-67d0-4b09-859f-4f4a1418f176')
+    def test_show_default_quota(self):
+        default_quotas = self.admin_quota_classes_client.show_quota_class_set(
+            'default')['quota_class_set']
+        self.assertIn('id', default_quotas)
+        self.assertEqual('default', default_quotas.pop('id'))
+        for key in QUOTA_KEYS:
+            self.assertIn(key, default_quotas)
+
+    @decorators.idempotent_id('a7644c63-2669-467a-b00e-452dd5c5397b')
+    def test_update_default_quota(self):
+        LOG.debug("Get the current default quota class values")
+        body = self.admin_quota_classes_client.show_quota_class_set(
+            'default')['quota_class_set']
+        body.pop('id')
+
+        # Restore the defaults when the test is done
+        self.addCleanup(self._restore_default_quotas, body.copy())
+
+        # Increment some of the values for updating the default quota class.
+        # For safety, only items with value >= 0 will be updated, and items
+        # with value < 0 (-1 means unlimited) will be ignored.
+        for quota, default in body.items():
+            if default >= 0:
+                body[quota] = default + 1
+
+        LOG.debug("Update limits for the default quota class set")
+        update_body = self.admin_quota_classes_client.update_quota_class_set(
+            'default', **body)['quota_class_set']
+        self.assertThat(update_body.items(),
+                        matchers.ContainsAll(body.items()))
+
+        # Verify current project's default quotas
+        default_quotas = self.admin_quotas_client.show_default_quota_set(
+            self.os_adm.credentials.tenant_id)['quota_set']
+        self.assertThat(default_quotas.items(),
+                        matchers.ContainsAll(body.items()))
+
+        # Verify a new project's default quotas
+        project_name = data_utils.rand_name('quota_class_tenant')
+        description = data_utils.rand_name('desc_')
+        project_id = self.identity_utils.create_project(
+            name=project_name, description=description)['id']
+        self.addCleanup(self.identity_utils.delete_project, project_id)
+        default_quotas = self.admin_quotas_client.show_default_quota_set(
+            project_id)['quota_set']
+        self.assertThat(default_quotas.items(),
+                        matchers.ContainsAll(body.items()))
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index 58ca92f..48941c6 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -13,6 +13,7 @@
 #    under the License.
 
 from tempest.api.volume import base
+from tempest.common import tempest_fixtures as fixtures
 from tempest.common import waiters
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -26,6 +27,11 @@
 
     credentials = ['primary', 'alt', 'admin']
 
+    def setUp(self):
+        # NOTE(jeremy.zhang): Avoid conflicts with volume quota class tests.
+        self.useFixture(fixtures.LockFixture('volume_quotas'))
+        super(BaseVolumeQuotasAdminTestJSON, self).setUp()
+
     @classmethod
     def setup_credentials(cls):
         super(BaseVolumeQuotasAdminTestJSON, cls).setup_credentials()
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index a19af5d..0ba5c6f 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -255,6 +255,8 @@
         cls.admin_backups_client = cls.os_adm.backups_v2_client
         cls.admin_encryption_types_client = \
             cls.os_adm.encryption_types_v2_client
+        cls.admin_quota_classes_client = \
+            cls.os_adm.volume_quota_classes_v2_client
         cls.admin_quotas_client = cls.os_adm.volume_quotas_v2_client
         cls.admin_volume_limits_client = cls.os_adm.volume_v2_limits_client
         cls.admin_capabilities_client = \
diff --git a/tempest/clients.py b/tempest/clients.py
index d21e6a7..5ba888d 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -274,6 +274,8 @@
         self.volume_hosts_v2_client = self.volume_v2.HostsClient()
         self.volume_quotas_client = self.volume_v1.QuotasClient()
         self.volume_quotas_v2_client = self.volume_v2.QuotasClient()
+        self.volume_quota_classes_v2_client = \
+            self.volume_v2.QuotaClassesClient()
         self.volumes_extension_client = self.volume_v1.ExtensionsClient()
         self.volumes_v2_extension_client = self.volume_v2.ExtensionsClient()
         self.volume_availability_zone_client = \
diff --git a/tempest/lib/services/volume/v2/__init__.py b/tempest/lib/services/volume/v2/__init__.py
index 9434896..68982d9 100644
--- a/tempest/lib/services/volume/v2/__init__.py
+++ b/tempest/lib/services/volume/v2/__init__.py
@@ -23,6 +23,8 @@
 from tempest.lib.services.volume.v2.hosts_client import HostsClient
 from tempest.lib.services.volume.v2.limits_client import LimitsClient
 from tempest.lib.services.volume.v2.qos_client import QosSpecsClient
+from tempest.lib.services.volume.v2.quota_classes_client import \
+    QuotaClassesClient
 from tempest.lib.services.volume.v2.quotas_client import QuotasClient
 from tempest.lib.services.volume.v2.scheduler_stats_client import \
     SchedulerStatsClient
@@ -40,4 +42,5 @@
            'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient',
            'ServicesClient', 'SnapshotsClient', 'TypesClient', 'VolumesClient',
            'LimitsClient', 'CapabilitiesClient', 'SchedulerStatsClient',
-           'SnapshotManageClient', 'VolumeManageClient', 'TransfersClient']
+           'SnapshotManageClient', 'VolumeManageClient', 'TransfersClient',
+           'QuotaClassesClient']
diff --git a/tempest/lib/services/volume/v2/quota_classes_client.py b/tempest/lib/services/volume/v2/quota_classes_client.py
new file mode 100644
index 0000000..d40d2d9
--- /dev/null
+++ b/tempest/lib/services/volume/v2/quota_classes_client.py
@@ -0,0 +1,49 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class QuotaClassesClient(rest_client.RestClient):
+    """Volume quota class V2 client."""
+
+    api_version = "v2"
+
+    def show_quota_class_set(self, quota_class_id):
+        """List quotas for a quota class.
+
+        TODO: Current api-site doesn't contain this API description.
+        LP: https://bugs.launchpad.net/nova/+bug/1602400
+        """
+        url = 'os-quota-class-sets/%s' % quota_class_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_quota_class_set(self, quota_class_id, **kwargs):
+        """Update quotas for a quota class.
+
+        TODO: Current api-site doesn't contain this API description.
+        LP: https://bugs.launchpad.net/nova/+bug/1602400
+        """
+        url = 'os-quota-class-sets/%s' % quota_class_id
+        put_body = json.dumps({'quota_class_set': kwargs})
+        resp, body = self.put(url, put_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py b/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py
new file mode 100644
index 0000000..e715fcc
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py
@@ -0,0 +1,71 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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 copy
+
+from tempest.lib.services.volume.v2 import quota_classes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestQuotaClassesClient(base.BaseServiceTest):
+
+    FAKE_QUOTA_CLASS_SET = {
+        "id": "test",
+        "gigabytes": 2000,
+        "volumes": 200,
+        "snapshots": 50,
+        "backups": 20,
+        "backup_gigabytes": 1500,
+        "per_volume_gigabytes": 500,
+    }
+
+    def setUp(self):
+        super(TestQuotaClassesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = quota_classes_client.QuotaClassesClient(
+            fake_auth, 'volume', 'regionOne')
+
+    def _test_show_quota_class_set(self, bytes_body=False):
+        fake_body = {'quota_class_set': self.FAKE_QUOTA_CLASS_SET}
+        self.check_service_client_function(
+            self.client.show_quota_class_set,
+            'tempest.lib.common.rest_client.RestClient.get',
+            fake_body,
+            bytes_body,
+            quota_class_id="test")
+
+    def _test_update_quota_class_set(self, bytes_body=False):
+        fake_quota_class_set = copy.deepcopy(self.FAKE_QUOTA_CLASS_SET)
+        fake_quota_class_set.pop("id")
+        fake_body = {'quota_class_set': fake_quota_class_set}
+        self.check_service_client_function(
+            self.client.update_quota_class_set,
+            'tempest.lib.common.rest_client.RestClient.put',
+            fake_body,
+            bytes_body,
+            quota_class_id="test")
+
+    def test_show_quota_class_set_with_str_body(self):
+        self._test_show_quota_class_set()
+
+    def test_show_quota_class_set_with_bytes_body(self):
+        self._test_show_quota_class_set(bytes_body=True)
+
+    def test_update_quota_class_set_with_str_boy(self):
+        self._test_update_quota_class_set()
+
+    def test_update_quota_class_set_with_bytes_body(self):
+        self._test_update_quota_class_set(bytes_body=True)