From fdd345969409ae32c93974f5ca10daf7d27aede1 Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 20 Feb 2014 18:40:10 +0100 Subject: [PATCH] Add Cinder tests for quota sets This commit adds client for the Cinder quota API and adds tests to list and update the quotas. Change-Id: Ic53dbf6e6ef26c4275039091fc4db6a63f796dba Closes-Bug: #1252774 --- .../api/volume/admin/test_volume_quotas.py | 99 +++++++++++++++++++ tempest/api/volume/base.py | 1 + tempest/clients.py | 8 ++ .../volume/json/admin/volume_quotas_client.py | 79 +++++++++++++++ .../volume/xml/admin/volume_quotas_client.py | 70 +++++++++++++ 5 files changed, 257 insertions(+) create mode 100644 tempest/api/volume/admin/test_volume_quotas.py create mode 100644 tempest/services/volume/json/admin/volume_quotas_client.py create mode 100644 tempest/services/volume/xml/admin/volume_quotas_client.py diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py new file mode 100644 index 0000000000..31f673018e --- /dev/null +++ b/tempest/api/volume/admin/test_volume_quotas.py @@ -0,0 +1,99 @@ +# Copyright (C) 2014 eNovance SAS +# +# Author: Sylvain Baubeau +# +# 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 tempest.api.volume import base +from tempest import test + +QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes'] +QUOTA_USAGE_KEYS = ['reserved', 'limit', 'in_use'] + + +class VolumeQuotasAdminTestJSON(base.BaseVolumeV1AdminTest): + _interface = "json" + + @classmethod + def setUpClass(cls): + super(VolumeQuotasAdminTestJSON, cls).setUpClass() + cls.admin_volume_client = cls.os_adm.volumes_client + cls.demo_tenant_id = cls.isolated_creds.get_primary_user().get( + 'tenantId') + + @test.attr(type='gate') + def test_list_quotas(self): + resp, quotas = self.quotas_client.get_quota_set(self.demo_tenant_id) + self.assertEqual(200, resp.status) + for key in QUOTA_KEYS: + self.assertIn(key, quotas) + + @test.attr(type='gate') + def test_list_default_quotas(self): + resp, quotas = self.quotas_client.get_default_quota_set( + self.demo_tenant_id) + self.assertEqual(200, resp.status) + for key in QUOTA_KEYS: + self.assertIn(key, quotas) + + @test.attr(type='gate') + def test_update_all_quota_resources_for_tenant(self): + # Admin can update all the resource quota limits for a tenant + resp, default_quota_set = self.quotas_client.get_default_quota_set( + self.demo_tenant_id) + new_quota_set = {'gigabytes': 1009, + 'volumes': 11, + 'snapshots': 11} + + # Update limits for all quota resources + resp, quota_set = self.quotas_client.update_quota_set( + self.demo_tenant_id, + **new_quota_set) + + default_quota_set.pop('id') + self.addCleanup(self.quotas_client.update_quota_set, + self.demo_tenant_id, **default_quota_set) + self.assertEqual(200, resp.status) + self.assertEqual(new_quota_set, quota_set) + + @test.attr(type='gate') + def test_show_quota_usage(self): + resp, quota_usage = self.quotas_client.get_quota_usage(self.adm_tenant) + self.assertEqual(200, resp.status) + for key in QUOTA_KEYS: + self.assertIn(key, quota_usage) + for usage_key in QUOTA_USAGE_KEYS: + self.assertIn(usage_key, quota_usage[key]) + + @test.attr(type='gate') + def test_quota_usage(self): + resp, quota_usage = self.quotas_client.get_quota_usage( + self.demo_tenant_id) + + volume = self.create_volume(size=1) + self.addCleanup(self.admin_volume_client.delete_volume, + volume['id']) + + resp, new_quota_usage = self.quotas_client.get_quota_usage( + self.demo_tenant_id) + + self.assertEqual(200, resp.status) + self.assertEqual(quota_usage['volumes']['in_use'] + 1, + new_quota_usage['volumes']['in_use']) + + self.assertEqual(quota_usage['gigabytes']['in_use'] + 1, + new_quota_usage['gigabytes']['in_use']) + + +class VolumeQuotasAdminTestXML(VolumeQuotasAdminTestJSON): + _interface = "xml" diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py index 882497793d..2c6050cd62 100644 --- a/tempest/api/volume/base.py +++ b/tempest/api/volume/base.py @@ -145,6 +145,7 @@ class BaseVolumeV1AdminTest(BaseVolumeV1Test): cls.os_adm = clients.AdminManager(interface=cls._interface) cls.client = cls.os_adm.volume_types_client cls.hosts_client = cls.os_adm.volume_hosts_client + cls.quotas_client = cls.os_adm.volume_quotas_client class BaseVolumeV2Test(BaseVolumeTest): diff --git a/tempest/clients.py b/tempest/clients.py index 8db399a2d2..220b8137c8 100644 --- a/tempest/clients.py +++ b/tempest/clients.py @@ -156,6 +156,8 @@ from tempest.services.telemetry.xml.telemetry_client import \ TelemetryClientXML from tempest.services.volume.json.admin.volume_hosts_client import \ VolumeHostsClientJSON +from tempest.services.volume.json.admin.volume_quotas_client import \ + VolumeQuotasClientJSON from tempest.services.volume.json.admin.volume_types_client import \ VolumeTypesClientJSON from tempest.services.volume.json.backups_client import BackupsClientJSON @@ -167,6 +169,8 @@ from tempest.services.volume.v2.json.volumes_client import VolumesV2ClientJSON from tempest.services.volume.v2.xml.volumes_client import VolumesV2ClientXML from tempest.services.volume.xml.admin.volume_hosts_client import \ VolumeHostsClientXML +from tempest.services.volume.xml.admin.volume_quotas_client import \ + VolumeQuotasClientXML from tempest.services.volume.xml.admin.volume_types_client import \ VolumeTypesClientXML from tempest.services.volume.xml.backups_client import BackupsClientXML @@ -247,6 +251,8 @@ class Manager(manager.Manager): InstanceUsagesAuditLogClientXML(self.auth_provider) self.volume_hosts_client = VolumeHostsClientXML( self.auth_provider) + self.volume_quotas_client = VolumeQuotasClientXML( + self.auth_provider) self.volumes_extension_client = VolumeExtensionClientXML( self.auth_provider) if CONF.service_available.ceilometer: @@ -327,6 +333,8 @@ class Manager(manager.Manager): InstanceUsagesAuditLogClientJSON(self.auth_provider) self.volume_hosts_client = VolumeHostsClientJSON( self.auth_provider) + self.volume_quotas_client = VolumeQuotasClientJSON( + self.auth_provider) self.volumes_extension_client = VolumeExtensionClientJSON( self.auth_provider) self.hosts_v3_client = HostsV3ClientJSON(self.auth_provider) diff --git a/tempest/services/volume/json/admin/volume_quotas_client.py b/tempest/services/volume/json/admin/volume_quotas_client.py new file mode 100644 index 0000000000..ea9c92e23f --- /dev/null +++ b/tempest/services/volume/json/admin/volume_quotas_client.py @@ -0,0 +1,79 @@ +# Copyright (C) 2014 eNovance SAS +# +# Author: Sylvain Baubeau +# +# 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 urllib + +from tempest.openstack.common import jsonutils + +from tempest.common import rest_client +from tempest import config + +CONF = config.CONF + + +class VolumeQuotasClientJSON(rest_client.RestClient): + """ + Client class to send CRUD Volume Quotas API requests to a Cinder endpoint + """ + + TYPE = "json" + + def __init__(self, auth_provider): + super(VolumeQuotasClientJSON, self).__init__(auth_provider) + + self.service = CONF.volume.catalog_type + self.build_interval = CONF.volume.build_interval + self.build_timeout = CONF.volume.build_timeout + + def get_default_quota_set(self, tenant_id): + """List the default volume quota set for a tenant.""" + + url = 'os-quota-sets/%s/defaults' % tenant_id + resp, body = self.get(url) + return resp, self._parse_resp(body) + + def get_quota_set(self, tenant_id, params=None): + """List the quota set for a tenant.""" + + url = 'os-quota-sets/%s' % tenant_id + if params: + url += '?%s' % urllib.urlencode(params) + + resp, body = self.get(url) + return resp, self._parse_resp(body) + + def get_quota_usage(self, tenant_id): + """List the quota set for a tenant.""" + + resp, body = self.get_quota_set(tenant_id, params={'usage': True}) + return resp, body + + def update_quota_set(self, tenant_id, gigabytes=None, volumes=None, + snapshots=None): + post_body = {} + + if gigabytes is not None: + post_body['gigabytes'] = gigabytes + + if volumes is not None: + post_body['volumes'] = volumes + + if snapshots is not None: + post_body['snapshots'] = snapshots + + post_body = jsonutils.dumps({'quota_set': post_body}) + resp, body = self.put('os-quota-sets/%s' % tenant_id, post_body) + return resp, self._parse_resp(body) diff --git a/tempest/services/volume/xml/admin/volume_quotas_client.py b/tempest/services/volume/xml/admin/volume_quotas_client.py new file mode 100644 index 0000000000..d2eac34d6c --- /dev/null +++ b/tempest/services/volume/xml/admin/volume_quotas_client.py @@ -0,0 +1,70 @@ +# Copyright (C) 2014 eNovance SAS +# +# Author: Sylvain Baubeau +# +# 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 ast import literal_eval +from lxml import etree + +from tempest import config +from tempest.services.compute.xml import common as xml +from tempest.services.volume.json.admin import volume_quotas_client + +CONF = config.CONF + + +class VolumeQuotasClientXML(volume_quotas_client.VolumeQuotasClientJSON): + """ + Client class to send CRUD Volume Quotas API requests to a Cinder endpoint + """ + + TYPE = "xml" + + def _format_quota(self, q): + quota = {} + for k, v in q.items(): + try: + v = literal_eval(v) + except (ValueError, SyntaxError): + pass + + quota[k] = v + + return quota + + def get_quota_usage(self, tenant_id): + """List the quota set for a tenant.""" + + resp, body = self.get_quota_set(tenant_id, params={'usage': True}) + return resp, self._format_quota(body) + + def update_quota_set(self, tenant_id, gigabytes=None, volumes=None, + snapshots=None): + post_body = {} + element = xml.Element("quota_set") + + if gigabytes is not None: + post_body['gigabytes'] = gigabytes + + if volumes is not None: + post_body['volumes'] = volumes + + if snapshots is not None: + post_body['snapshots'] = snapshots + + xml.deep_dict_to_xml(element, post_body) + resp, body = self.put('os-quota-sets/%s' % tenant_id, + str(xml.Document(element))) + body = xml.xml_to_json(etree.fromstring(body)) + return resp, self._format_quota(body)