diff --git a/releasenotes/notes/network-quotas-b98cce9ffeffdbf4.yaml b/releasenotes/notes/network-quotas-b98cce9ffeffdbf4.yaml new file mode 100644 index 000000000..a58cbeab4 --- /dev/null +++ b/releasenotes/notes/network-quotas-b98cce9ffeffdbf4.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add new APIs, OperatorCloud.get_network_quotas(), OperatorCloud.set_network_quotas() and OperatorCloud.delete_network_quotas() to manage neutron quotas for projects and users \ No newline at end of file diff --git a/shade/_tasks.py b/shade/_tasks.py index 4783c04c0..632f55331 100644 --- a/shade/_tasks.py +++ b/shade/_tasks.py @@ -909,6 +909,21 @@ class CinderQuotasDelete(task_manager.Task): return client.cinder_client.quotas.delete(**self.args) +class NeutronQuotasSet(task_manager.Task): + def main(self, client): + return client.neutron_client.update_quota(**self.args) + + +class NeutronQuotasGet(task_manager.Task): + def main(self, client): + return client.neutron_client.show_quota(**self.args)['quota'] + + +class NeutronQuotasDelete(task_manager.Task): + def main(self, client): + return client.neutron_client.delete_quota(**self.args) + + class BaymodelList(task_manager.Task): def main(self, client): return client.magnum_client.baymodels.list(**self.args) diff --git a/shade/operatorcloud.py b/shade/operatorcloud.py index babadb98e..8607a4b23 100644 --- a/shade/operatorcloud.py +++ b/shade/operatorcloud.py @@ -2053,3 +2053,54 @@ class OperatorCloud(openstackcloud.OpenStackCloud): _tasks.CinderQuotasDelete(tenant_id=proj.id)) except cinder_exceptions.BadRequest: raise OpenStackCloudException("cinder client call failed") + + def set_network_quotas(self, name_or_id, **kwargs): + """ Set a network 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") + + body = {'quota': kwargs} + with _utils.neutron_exceptions("network client call failed"): + self.manager.submitTask( + _tasks.NeutronQuotasSet(tenant_id=proj.id, + body=body)) + + def get_network_quotas(self, name_or_id): + """ Get network 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") + with _utils.neutron_exceptions("network client call failed"): + return self.manager.submitTask( + _tasks.NeutronQuotasGet(tenant_id=proj.id)) + + def delete_network_quotas(self, name_or_id): + """ Delete network quotas for a project + + :param name_or_id: project name or id + :raises: OpenStackCloudException if it's not a valid project or the + network client call failed + + :returns: dict with the quotas + """ + proj = self.get_project(name_or_id) + if not proj: + raise OpenStackCloudException("project does not exist") + with _utils.neutron_exceptions("network client call failed"): + return self.manager.submitTask( + _tasks.NeutronQuotasDelete(tenant_id=proj.id)) diff --git a/shade/tests/functional/test_quotas.py b/shade/tests/functional/test_quotas.py index aaf732dc1..b99e73ea2 100644 --- a/shade/tests/functional/test_quotas.py +++ b/shade/tests/functional/test_quotas.py @@ -60,3 +60,25 @@ class TestVolumeQuotas(base.TestCase): self.cloud.delete_volume_quotas('demo') self.assertEqual(volumes, self.cloud.get_volume_quotas('demo')['volumes']) + + +class TestNetworkQuotas(base.TestCase): + + def setUp(self): + super(TestNetworkQuotas, self).setUp() + self.cloud = operator_cloud(cloud='devstack-admin') + if not self.cloud.has_service('network'): + self.skipTest('network service not supported by cloud') + + def test_quotas(self): + '''Test quotas functionality''' + quotas = self.cloud.get_network_quotas('demo') + network = quotas['network'] + self.cloud.set_network_quotas('demo', network=network + 1) + self.assertEqual(network + 1, + self.cloud.get_network_quotas('demo')['network'] + ) + self.cloud.delete_network_quotas('demo') + self.assertEqual(network, + self.cloud.get_network_quotas('demo')['network'] + ) diff --git a/shade/tests/unit/test_quotas.py b/shade/tests/unit/test_quotas.py index b07a18d73..01160e37e 100644 --- a/shade/tests/unit/test_quotas.py +++ b/shade/tests/unit/test_quotas.py @@ -82,3 +82,33 @@ class TestQuotas(base.TestCase): mock_cinder.quotas.delete.assert_called_once_with( tenant_id='project_a') + + @mock.patch.object(shade.OpenStackCloud, 'neutron_client') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_neutron_update_quotas(self, mock_keystone, mock_neutron): + project = fakes.FakeProject('project_a') + mock_keystone.tenants.list.return_value = [project] + self.cloud.set_network_quotas(project, network=1) + + mock_neutron.update_quota.assert_called_once_with( + body={'quota': {'network': 1}}, tenant_id='project_a') + + @mock.patch.object(shade.OpenStackCloud, 'neutron_client') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_neutron_get_quotas(self, mock_keystone, mock_neutron): + project = fakes.FakeProject('project_a') + mock_keystone.tenants.list.return_value = [project] + self.cloud.get_network_quotas(project) + + mock_neutron.show_quota.assert_called_once_with( + tenant_id='project_a') + + @mock.patch.object(shade.OpenStackCloud, 'neutron_client') + @mock.patch.object(shade.OpenStackCloud, 'keystone_client') + def test_neutron_delete_quotas(self, mock_keystone, mock_neutron): + project = fakes.FakeProject('project_a') + mock_keystone.tenants.list.return_value = [project] + self.cloud.delete_network_quotas(project) + + mock_neutron.delete_quota.assert_called_once_with( + tenant_id='project_a')