diff --git a/magnum/api/controllers/v1/quota.py b/magnum/api/controllers/v1/quota.py index 6581a2ea9f..18139f4a7b 100644 --- a/magnum/api/controllers/v1/quota.py +++ b/magnum/api/controllers/v1/quota.py @@ -23,6 +23,7 @@ from magnum.api.controllers.v1 import collection from magnum.api.controllers.v1 import types from magnum.api import expose from magnum.api import utils as api_utils +from magnum.api import validation from magnum.common import exception from magnum.common import policy from magnum.i18n import _ @@ -164,6 +165,7 @@ class QuotaController(base.Controller): return Quota.convert(quota) @expose.expose(Quota, body=Quota, status_code=201) + @validation.enforce_valid_project_id_on_create() def post(self, quota): """Create Quota. diff --git a/magnum/api/validation.py b/magnum/api/validation.py index 234bba8ba1..300bfbbb9e 100644 --- a/magnum/api/validation.py +++ b/magnum/api/validation.py @@ -17,7 +17,10 @@ import decorator import pecan +from keystoneauth1 import exceptions as ka_exception + from magnum.api import utils as api_utils +from magnum.common import clients from magnum.common import exception import magnum.conf from magnum.drivers.common import driver @@ -44,6 +47,26 @@ def enforce_cluster_type_supported(): return wrapper +def enforce_valid_project_id_on_create(): + @decorator.decorator + def wrapper(func, *args, **kwargs): + quota = args[1] + _validate_project_id(quota.project_id) + return func(*args, **kwargs) + + return wrapper + + +def _validate_project_id(project_id): + try: + context = pecan.request.context + osc = clients.OpenStackClients(context) + osc.keystone().domain_admin_client.projects.get(project_id) + except ka_exception.http.NotFound: + raise exception.ProjectNotFound(name='project_id', + id=project_id) + + def enforce_network_driver_types_create(): @decorator.decorator def wrapper(func, *args, **kwargs): diff --git a/magnum/common/exception.py b/magnum/common/exception.py index d3986494b1..e337700689 100644 --- a/magnum/common/exception.py +++ b/magnum/common/exception.py @@ -124,6 +124,11 @@ class ObjectNotFound(MagnumException): message = _("The %(name)s %(id)s could not be found.") +class ProjectNotFound(ObjectNotFound): + message = _("The %(name)s %(id)s could not be found.") + code = 404 + + class ResourceNotFound(ObjectNotFound): message = _("The %(name)s resource %(id)s could not be found.") code = 404 diff --git a/magnum/tests/unit/api/controllers/v1/test_quota.py b/magnum/tests/unit/api/controllers/v1/test_quota.py index 903ba3d80d..e516551c2e 100644 --- a/magnum/tests/unit/api/controllers/v1/test_quota.py +++ b/magnum/tests/unit/api/controllers/v1/test_quota.py @@ -12,7 +12,10 @@ import mock +from keystoneauth1 import exceptions as ka_exception + from magnum.api.controllers.v1 import quota as api_quota +from magnum.common import clients from magnum.tests import base from magnum.tests.unit.api import base as api_base from magnum.tests.unit.api import utils as apiutils @@ -169,14 +172,28 @@ class TestQuota(api_base.FunctionalTest): self.assertEqual(1, len(response['quotas'])) self.assertEqual('proj-id-2', response['quotas'][0]['project_id']) - def test_create_quota(self): + @mock.patch.object(clients.OpenStackClients, 'keystone') + def test_create_quota(self, mock_keystone): quota_dict = apiutils.quota_post_data() response = self.post_json('/quotas', quota_dict) self.assertEqual('application/json', response.content_type) self.assertEqual(201, response.status_int) self.assertEqual(quota_dict['project_id'], response.json['project_id']) - def test_create_quota_invalid_resource(self): + @mock.patch.object(clients.OpenStackClients, 'keystone') + def test_create_quota_project_id_not_found(self, mock_keystone): + keystone = mock.MagicMock() + exp = ka_exception.http.NotFound() + keystone.domain_admin_client.projects .get.side_effect = exp + mock_keystone.return_value = keystone + quota_dict = apiutils.quota_post_data() + response = self.post_json('/quotas', quota_dict, expect_errors=True) + self.assertEqual('application/json', response.content_type) + self.assertEqual(404, response.status_int) + self.assertTrue(response.json['errors']) + + @mock.patch.object(clients.OpenStackClients, 'keystone') + def test_create_quota_invalid_resource(self, mock_keystone): quota_dict = apiutils.quota_post_data() quota_dict['resource'] = 'invalid-res' response = self.post_json('/quotas', quota_dict, expect_errors=True) @@ -184,7 +201,8 @@ class TestQuota(api_base.FunctionalTest): self.assertEqual(400, response.status_int) self.assertTrue(response.json['errors']) - def test_create_quota_invalid_hard_limit(self): + @mock.patch.object(clients.OpenStackClients, 'keystone') + def test_create_quota_invalid_hard_limit(self, mock_keystone): quota_dict = apiutils.quota_post_data() quota_dict['hard_limit'] = -10 response = self.post_json('/quotas', quota_dict, expect_errors=True) @@ -192,7 +210,8 @@ class TestQuota(api_base.FunctionalTest): self.assertEqual(400, response.status_int) self.assertTrue(response.json['errors']) - def test_create_quota_no_project_id(self): + @mock.patch.object(clients.OpenStackClients, 'keystone') + def test_create_quota_no_project_id(self, mock_keystone): quota_dict = apiutils.quota_post_data() del quota_dict['project_id'] response = self.post_json('/quotas', quota_dict, expect_errors=True) @@ -200,7 +219,8 @@ class TestQuota(api_base.FunctionalTest): self.assertEqual(400, response.status_int) self.assertTrue(response.json['errors']) - def test_patch_quota(self): + @mock.patch.object(clients.OpenStackClients, 'keystone') + def test_patch_quota(self, mock_keystone): quota_dict = apiutils.quota_post_data(hard_limit=5) response = self.post_json('/quotas', quota_dict) self.assertEqual('application/json', response.content_type) @@ -214,7 +234,8 @@ class TestQuota(api_base.FunctionalTest): self.assertEqual(202, response.status_int) self.assertEqual(20, response.json['hard_limit']) - def test_patch_quota_not_found(self): + @mock.patch.object(clients.OpenStackClients, 'keystone') + def test_patch_quota_not_found(self, mock_keystone): quota_dict = apiutils.quota_post_data() response = self.post_json('/quotas', quota_dict) self.assertEqual('application/json', response.content_type) @@ -229,7 +250,8 @@ class TestQuota(api_base.FunctionalTest): self.assertEqual(404, response.status_int) self.assertTrue(response.json['errors']) - def test_delete_quota(self): + @mock.patch.object(clients.OpenStackClients, 'keystone') + def test_delete_quota(self, mock_keystone): quota_dict = apiutils.quota_post_data() response = self.post_json('/quotas', quota_dict) self.assertEqual('application/json', response.content_type)