From 49ca65001565133aa20ebf525e2d05f5df99d7f4 Mon Sep 17 00:00:00 2001 From: Ghe Rivero Date: Fri, 20 May 2016 09:28:03 +0200 Subject: [PATCH] Add volume quotas support Add the capability to get, update and reset to default quotas in volume service. Change-Id: Ib5a996b90bb41c7ad01ac496a89478ea71a6bf2f --- .../notes/volume-quotas-5b674ee8c1f71eb6.yaml | 3 + shade/_tasks.py | 15 +++++ shade/operatorcloud.py | 57 +++++++++++++++++++ shade/tests/functional/test_quotas.py | 20 +++++++ shade/tests/unit/test_quotas.py | 29 ++++++++++ 5 files changed, 124 insertions(+) create mode 100644 releasenotes/notes/volume-quotas-5b674ee8c1f71eb6.yaml diff --git a/releasenotes/notes/volume-quotas-5b674ee8c1f71eb6.yaml b/releasenotes/notes/volume-quotas-5b674ee8c1f71eb6.yaml new file mode 100644 index 000000000..dfb3b1cd6 --- /dev/null +++ b/releasenotes/notes/volume-quotas-5b674ee8c1f71eb6.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add new APIs, OperatorCloud.get_volume_quotas(), OperatorCloud.set_volume_quotas() and OperatorCloud.delete_volume_quotas() to manage cinder quotas for projects and users \ No newline at end of file diff --git a/shade/_tasks.py b/shade/_tasks.py index 03faec340..6ddb8f57a 100644 --- a/shade/_tasks.py +++ b/shade/_tasks.py @@ -892,3 +892,18 @@ class NovaQuotasGet(task_manager.Task): class NovaQuotasDelete(task_manager.Task): def main(self, client): return client.nova_client.quotas.delete(**self.args) + + +class CinderQuotasSet(task_manager.Task): + def main(self, client): + return client.cinder_client.quotas.update(**self.args) + + +class CinderQuotasGet(task_manager.Task): + def main(self, client): + return client.cinder_client.quotas.get(**self.args) + + +class CinderQuotasDelete(task_manager.Task): + def main(self, client): + return client.cinder_client.quotas.delete(**self.args) diff --git a/shade/operatorcloud.py b/shade/operatorcloud.py index 91df905c1..babadb98e 100644 --- a/shade/operatorcloud.py +++ b/shade/operatorcloud.py @@ -15,6 +15,7 @@ import jsonpatch from ironicclient import client as ironic_client from ironicclient import exceptions as ironic_exceptions from novaclient import exceptions as nova_exceptions +from cinderclient import exceptions as cinder_exceptions from shade.exc import * # noqa from shade import openstackcloud @@ -1996,3 +1997,59 @@ class OperatorCloud(openstackcloud.OpenStackCloud): _tasks.NovaQuotasDelete(tenant_id=proj.id)) except novaclient.exceptions.BadRequest: raise OpenStackCloudException("nova client call failed") + + def set_volume_quotas(self, name_or_id, **kwargs): + """ Set a volume quota in a project + + :param name_or_id: project name or id + :param kwargs: key/value pairs of quota name and quota value + + :raises: OpenStackCloudException if the resource to set the + quota does not exist. + """ + + proj = self.get_project(name_or_id) + if not proj: + raise OpenStackCloudException("project does not exist") + + try: + self.manager.submitTask( + _tasks.CinderQuotasSet(tenant_id=proj.id, + **kwargs)) + except cinder_exceptions.BadRequest: + raise OpenStackCloudException("No valid quota or resource") + + def get_volume_quotas(self, name_or_id): + """ Get volume quotas for a project + + :param name_or_id: project name or id + :raises: OpenStackCloudException if it's not a valid project + + :returns: Munch object with the quotas + """ + proj = self.get_project(name_or_id) + if not proj: + raise OpenStackCloudException("project does not exist") + try: + return self.manager.submitTask( + _tasks.CinderQuotasGet(tenant_id=proj.id)) + except cinder_exceptions.BadRequest: + raise OpenStackCloudException("cinder client call failed") + + def delete_volume_quotas(self, name_or_id): + """ Delete volume quotas for a project + + :param name_or_id: project name or id + :raises: OpenStackCloudException if it's not a valid project or the + cinder client call failed + + :returns: dict with the quotas + """ + proj = self.get_project(name_or_id) + if not proj: + raise OpenStackCloudException("project does not exist") + try: + return self.manager.submitTask( + _tasks.CinderQuotasDelete(tenant_id=proj.id)) + except cinder_exceptions.BadRequest: + raise OpenStackCloudException("cinder client call failed") diff --git a/shade/tests/functional/test_quotas.py b/shade/tests/functional/test_quotas.py index 0609ed296..aaf732dc1 100644 --- a/shade/tests/functional/test_quotas.py +++ b/shade/tests/functional/test_quotas.py @@ -40,3 +40,23 @@ class TestComputeQuotas(base.TestCase): self.cloud.get_compute_quotas('demo')['cores']) self.cloud.delete_compute_quotas('demo') self.assertEqual(cores, self.cloud.get_compute_quotas('demo')['cores']) + + +class TestVolumeQuotas(base.TestCase): + + def setUp(self): + super(TestVolumeQuotas, self).setUp() + self.cloud = operator_cloud(cloud='devstack-admin') + if not self.cloud.has_service('volume'): + self.skipTest('volume service not supported by cloud') + + def test_quotas(self): + '''Test quotas functionality''' + quotas = self.cloud.get_volume_quotas('demo') + volumes = quotas['volumes'] + self.cloud.set_volume_quotas('demo', volumes=volumes + 1) + self.assertEqual(volumes + 1, + self.cloud.get_volume_quotas('demo')['volumes']) + self.cloud.delete_volume_quotas('demo') + self.assertEqual(volumes, + self.cloud.get_volume_quotas('demo')['volumes']) diff --git a/shade/tests/unit/test_quotas.py b/shade/tests/unit/test_quotas.py index be43f2fad..b07a18d73 100644 --- a/shade/tests/unit/test_quotas.py +++ b/shade/tests/unit/test_quotas.py @@ -53,3 +53,32 @@ class TestQuotas(base.TestCase): self.cloud.delete_compute_quotas(project) mock_nova.quotas.delete.assert_called_once_with(tenant_id='project_a') + + @mock.patch.object(shade.OpenStackCloud, 'cinder_client') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_cinder_update_quotas(self, mock_keystone, mock_cinder): + project = fakes.FakeProject('project_a') + mock_keystone.tenants.list.return_value = [project] + self.cloud.set_volume_quotas(project, volumes=1) + + mock_cinder.quotas.update.assert_called_once_with( + volumes=1, tenant_id='project_a') + + @mock.patch.object(shade.OpenStackCloud, 'cinder_client') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_cinder_get_quotas(self, mock_keystone, mock_cinder): + project = fakes.FakeProject('project_a') + mock_keystone.tenants.list.return_value = [project] + self.cloud.get_volume_quotas(project) + + mock_cinder.quotas.get.assert_called_once_with(tenant_id='project_a') + + @mock.patch.object(shade.OpenStackCloud, 'cinder_client') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_cinder_delete_quotas(self, mock_keystone, mock_cinder): + project = fakes.FakeProject('project_a') + mock_keystone.tenants.list.return_value = [project] + self.cloud.delete_volume_quotas(project) + + mock_cinder.quotas.delete.assert_called_once_with( + tenant_id='project_a')