Extend Quota API to report usage statistics

Extend existing quota api to report a quota set. The quota set
will contain a set of resources and its corresponding reservation,
limits and in_use count for each tenant.

DocImpact:Documentation describing the new API as well as the new
information that it exposes.
APIImpact

Co-Authored-By: Prince Boateng<prince.a.owusu.boateng@intel.com>
Change-Id: Ief2a6a4d2d7085e2a9dcd901123bc4fe6ac7ca22
Related-bug: #1599488
diff --git a/neutron/tests/tempest/api/admin/test_quotas.py b/neutron/tests/tempest/api/admin/test_quotas.py
index 85aecb9..fe8f511 100644
--- a/neutron/tests/tempest/api/admin/test_quotas.py
+++ b/neutron/tests/tempest/api/admin/test_quotas.py
@@ -13,9 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import six
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
+from tempest import test
 
 from neutron.tests.tempest.api import base
 from neutron.tests.tempest import config
@@ -58,6 +60,19 @@
         except lib_exc.NotFound:
             pass
 
+    def _create_network(self, project_id):
+        network = self.create_network(client=self.admin_client,
+                                      tenant_id=project_id)
+        self.addCleanup(self.admin_client.delete_network,
+                        network['id'])
+        return network
+
+    def _create_port(self, **kwargs):
+        port = self.admin_client.create_port(**kwargs)['port']
+        self.addCleanup(self.admin_client.delete_port,
+                        port['id'])
+        return port
+
 
 class QuotasTest(QuotasTestBase):
     """Test the Neutron API of Quotas.
@@ -67,6 +82,7 @@
 
         list quotas for tenants who have non-default quota values
         show quotas for a specified tenant
+        show detail quotas for a specified tenant
         update quotas for a specified tenant
         reset quotas to default values for a specified tenant
 
@@ -108,3 +124,39 @@
         non_default_quotas = self.admin_client.list_quotas()
         for q in non_default_quotas['quotas']:
             self.assertNotEqual(tenant_id, q['tenant_id'])
+
+    @decorators.idempotent_id('e974b5ba-090a-452c-a578-f9710151d9fc')
+    @decorators.attr(type='gate')
+    @test.requires_ext(extension="quota_details", service="network")
+    def test_detail_quotas(self):
+        tenant_id = self._create_tenant()['id']
+        new_quotas = {'network': {'used': 1, 'limit': 2, 'reserved': 0},
+                      'port': {'used': 1, 'limit': 2, 'reserved': 0}}
+
+        # update quota limit for tenant
+        new_quota = {'network': new_quotas['network']['limit'], 'port':
+                     new_quotas['port']['limit']}
+        quota_set = self._setup_quotas(tenant_id, **new_quota)
+
+        # create test resources
+        network = self._create_network(tenant_id)
+        post_body = {"network_id": network['id'],
+                     "tenant_id": tenant_id}
+        self._create_port(**post_body)
+
+        # confirm from extended API quotas were changed
+        # as requested for tenant
+        quota_set = self.admin_client.show_details_quota(tenant_id)
+        quota_set = quota_set['quota']
+        for key, value in six.iteritems(new_quotas):
+            self.assertEqual(new_quotas[key]['limit'],
+                             quota_set[key]['limit'])
+            self.assertEqual(new_quotas[key]['reserved'],
+                             quota_set[key]['reserved'])
+            self.assertEqual(new_quotas[key]['used'],
+                             quota_set[key]['used'])
+
+        # validate 'default' action for old extension
+        quota_limit = self.admin_client.show_quotas(tenant_id)['quota']
+        for key, value in six.iteritems(new_quotas):
+            self.assertEqual(new_quotas[key]['limit'], quota_limit[key])
diff --git a/neutron/tests/tempest/api/base.py b/neutron/tests/tempest/api/base.py
index 84746fb..0ec71c3 100644
--- a/neutron/tests/tempest/api/base.py
+++ b/neutron/tests/tempest/api/base.py
@@ -217,11 +217,12 @@
             pass
 
     @classmethod
-    def create_network(cls, network_name=None, **kwargs):
+    def create_network(cls, network_name=None, client=None, **kwargs):
         """Wrapper utility that returns a test network."""
         network_name = network_name or data_utils.rand_name('test-network-')
 
-        body = cls.client.create_network(name=network_name, **kwargs)
+        client = client or cls.client
+        body = client.create_network(name=network_name, **kwargs)
         network = body['network']
         cls.networks.append(network)
         return network
diff --git a/neutron/tests/tempest/services/network/json/network_client.py b/neutron/tests/tempest/services/network/json/network_client.py
index 39f2c38..12cdf97 100644
--- a/neutron/tests/tempest/services/network/json/network_client.py
+++ b/neutron/tests/tempest/services/network/json/network_client.py
@@ -124,7 +124,13 @@
             # list of field's name. An example:
             # {'fields': ['id', 'name']}
             plural = self.pluralize(resource_name)
-            uri = '%s/%s' % (self.get_uri(plural), resource_id)
+            if 'details_quotas' in plural:
+                details, plural = plural.split('_')
+                uri = '%s/%s/%s' % (self.get_uri(plural),
+                                    resource_id, details)
+            else:
+                uri = '%s/%s' % (self.get_uri(plural), resource_id)
+
             if fields:
                 uri += '?' + urlparse.urlencode(fields, doseq=1)
             resp, body = self.get(uri)