Add quotas client + tests, for the admin api

Change-Id: I7743f97b919511dc0c9d06415838ab4c3dfcac33
diff --git a/.gitignore b/.gitignore
index 8ae2b42..a6a0b0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,13 +14,13 @@
 *.log
 .coverage
 .coverage.*
-.testrepository/*
+/.testrepository/*
 .tox
 .venv
 cover
 flake8_results.html
-functionaltests/.testrepository/
-functionaltests/tempest.log
+tempest.log
+tempest.conf
 htmlcov/
 venv
 
diff --git a/designate_tempest_plugin/clients.py b/designate_tempest_plugin/clients.py
index 8e83bcf..d042f65 100644
--- a/designate_tempest_plugin/clients.py
+++ b/designate_tempest_plugin/clients.py
@@ -20,6 +20,8 @@
     ZoneImportsClient
 from designate_tempest_plugin.services.dns.v2.json.blacklists_client import \
     BlacklistsClient
+from designate_tempest_plugin.services.dns.admin.json.quotas_client import \
+    QuotasClient
 
 CONF = config.CONF
 
@@ -41,3 +43,4 @@
         self.zone_imports_client = ZoneImportsClient(self.auth_provider,
                                                      **params)
         self.blacklists_client = BlacklistsClient(self.auth_provider, **params)
+        self.quotas_client = QuotasClient(self.auth_provider, **params)
diff --git a/designate_tempest_plugin/data_utils.py b/designate_tempest_plugin/data_utils.py
index 8b18785..c77c4b1 100644
--- a/designate_tempest_plugin/data_utils.py
+++ b/designate_tempest_plugin/data_utils.py
@@ -11,9 +11,11 @@
 # 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 tempest.lib.common.utils import data_utils
 
+LOG = logging.getLogger(__name__)
+
 
 def rand_zone_name(name='', prefix=None, suffix='.com.'):
     """Generate a random zone name
@@ -57,3 +59,24 @@
         ttl = str(rand_ttl(1200, 8400))
 
     return zone_base.replace('&', name).replace('#', ttl)
+
+
+def rand_quotas(zones=None, zone_records=None, zone_recordsets=None,
+                recordset_records=None, api_export_size=None):
+    LOG.warn("Leaving `api_export_size` out of quota data due to: "
+             "https://bugs.launchpad.net/designate/+bug/1573141")
+    return {
+        'quota': {
+            'zones':
+                zones or data_utils.rand_int_id(100, 999999),
+            'zone_records':
+                zone_records or data_utils.rand_int_id(100, 999999),
+            'zone_recordsets':
+                zone_recordsets or data_utils.rand_int_id(100, 999999),
+            'recordset_records':
+                recordset_records or data_utils.rand_int_id(100, 999999),
+            # https://bugs.launchpad.net/designate/+bug/1573141
+            # 'api_export_size':
+            #     api_export_size or data_utils.rand_int_id(100, 999999),
+        }
+    }
diff --git a/designate_tempest_plugin/services/dns/admin/__init__.py b/designate_tempest_plugin/services/dns/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/admin/__init__.py
diff --git a/designate_tempest_plugin/services/dns/admin/json/__init__.py b/designate_tempest_plugin/services/dns/admin/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/admin/json/__init__.py
diff --git a/designate_tempest_plugin/services/dns/admin/json/base.py b/designate_tempest_plugin/services/dns/admin/json/base.py
new file mode 100644
index 0000000..2714625
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/admin/json/base.py
@@ -0,0 +1,21 @@
+# Copyright 2016 Rackspace
+#
+# 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 designate_tempest_plugin.services.dns.json import base
+
+handle_errors = base.handle_errors
+
+
+class DnsClientAdminBase(base.DnsClientBase):
+    """Base Admin API Tempest REST client for Designate API"""
+    uri_prefix = 'admin'
diff --git a/designate_tempest_plugin/services/dns/admin/json/quotas_client.py b/designate_tempest_plugin/services/dns/admin/json/quotas_client.py
new file mode 100644
index 0000000..c815611
--- /dev/null
+++ b/designate_tempest_plugin/services/dns/admin/json/quotas_client.py
@@ -0,0 +1,91 @@
+# Copyright 2016 Rackspace
+#
+# 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 designate_tempest_plugin import data_utils as dns_data_utils
+from designate_tempest_plugin.services.dns.admin.json import base
+
+LOG = logging.getLogger(__name__)
+
+
+class QuotasClient(base.DnsClientAdminBase):
+
+    @base.handle_errors
+    def update_quotas(self, zones=None, zone_records=None,
+                      zone_recordsets=None, recordset_records=None,
+                      api_export_size=None, project_id=None, params=None):
+        """Update the quotas for the project id
+
+        :param zones: The limit on zones per tenant
+            Default: Random Value
+        :param zone_records: The limit on records per zone
+            Default: Random Value
+        :param zone_recordsets: The limit recordsets per zone
+            Default: Random Value
+        :param recordset_records: The limit on records per recordset
+            Default: Random Value
+        :param api_export_size: The limit on size of on exported zone
+            Default: Random Value
+        :param project_id: Apply the quotas to this project id
+            Default: The project id of this client
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: A tuple with the server response and the created quota.
+        """
+        project_id = project_id or self.tenant_id
+
+        quotas = dns_data_utils.rand_quotas(
+            zones=zones,
+            zone_records=zone_records,
+            zone_recordsets=zone_recordsets,
+            recordset_records=recordset_records,
+            api_export_size=api_export_size,
+        )
+
+        resp, body = self._update_request('quotas', project_id, quotas,
+                                          params=params)
+
+        self.expected_success(200, resp.status)
+
+        return resp, body
+
+    @base.handle_errors
+    def show_quotas(self, project_id=None, params=None):
+        """Gets a specific quota.
+
+        :param project_id: Show the quotas of this project id
+            Default: The project id of this client
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: Serialized quota as a dictionary.
+        """
+        project_id = project_id or self.tenant_id
+        return self._show_request('quotas', project_id, params=params)
+
+    @base.handle_errors
+    def delete_quotas(self, project_id=None, params=None):
+        """Resets the quotas for the specified project id
+
+        :param project_id: Reset the quotas of this project id
+            Default: The project id of this client
+        :param params: A Python dict that represents the query paramaters to
+                       include in the request URI.
+        :return: A tuple with the server response and the response body.
+        """
+        project_id = project_id or self.tenant_id
+        resp, body = self._delete_request('quotas', project_id, params=params)
+
+        self.expected_success(204, resp.status)
+
+        return resp, body
diff --git a/designate_tempest_plugin/tests/api/admin/__init__.py b/designate_tempest_plugin/tests/api/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/admin/__init__.py
diff --git a/designate_tempest_plugin/tests/api/admin/test_quotas.py b/designate_tempest_plugin/tests/api/admin/test_quotas.py
new file mode 100644
index 0000000..e2d89af
--- /dev/null
+++ b/designate_tempest_plugin/tests/api/admin/test_quotas.py
@@ -0,0 +1,70 @@
+# Copyright 2016 Rackspace
+#
+# 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 tempest import test
+
+from designate_tempest_plugin.tests import base
+from designate_tempest_plugin import data_utils as dns_data_utils
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseQuotasTest(base.BaseDnsTest):
+    # see: https://bugs.launchpad.net/designate/+bug/1573141
+    excluded_keys = ['api_expected_size']
+
+
+class QuotasAdminTest(BaseQuotasTest):
+
+    credentials = ["admin"]
+
+    @classmethod
+    def setup_clients(cls):
+        super(QuotasAdminTest, cls).setup_clients()
+
+        cls.admin_client = cls.os_adm.quotas_client
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('ed42f367-e5ba-40d7-a08d-366ad787d21c')
+    def test_show_quotas(self):
+        LOG.info("Updating quotas")
+        quotas = dns_data_utils.rand_quotas()
+        _, body = self.admin_client.update_quotas(**quotas['quota'])
+        self.addCleanup(self.admin_client.delete_quotas)
+
+        LOG.info("Fetching quotas")
+        _, body = self.admin_client.show_quotas()
+
+        LOG.info("Ensuring the response has all quota types")
+        self.assertExpected(quotas['quota'], body['quota'], self.excluded_keys)
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('33e0affb-5d66-4216-881c-f101a779851a')
+    def test_delete_quotas(self):
+        LOG.info("Deleting quotas")
+        _, body = self.admin_client.delete_quotas()
+
+        LOG.info("Ensuring an empty response body")
+        self.assertEqual(body.strip(), "")
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('4f2b65b7-c4e1-489c-9047-755e42ba0985')
+    def test_update_quotas(self):
+        LOG.info("Updating quotas")
+        quotas = dns_data_utils.rand_quotas()
+        _, body = self.admin_client.update_quotas(**quotas['quota'])
+        self.addCleanup(self.admin_client.delete_quotas)
+
+        LOG.info("Ensuring the response has all quota types")
+        self.assertExpected(quotas['quota'], body['quota'], self.excluded_keys)