diff --git a/zun/api/controllers/base.py b/zun/api/controllers/base.py index 5c5931124..a41813ba4 100644 --- a/zun/api/controllers/base.py +++ b/zun/api/controllers/base.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +from zun.common import exception + class APIBase(object): @@ -20,6 +22,10 @@ class APIBase(object): if field in kwargs: value = kwargs[field] setattr(self, field, value) + else: + if self.fields[field].get('mandatory', False): + message = 'Required field %s is missing' % field + raise exception.ValidationError(detail=message) def __setattr__(self, field, value): if field in self.fields: diff --git a/zun/api/controllers/types.py b/zun/api/controllers/types.py index edddbfde4..1c95cd761 100644 --- a/zun/api/controllers/types.py +++ b/zun/api/controllers/types.py @@ -94,20 +94,6 @@ class NameType(String): raise exception.InvalidValue(message) -class ImageNameType(NameType): - type_name = 'ImageNameType' - # ImageNameType allows to be Non-None or a string matches pattern - # `[a-zA-Z0-9][a-zA-Z0-9_.-].` with minimum length is 2 and maximum length - # 255 string type. - - @classmethod - def validate(cls, value, pattern=None): - if value is None: - message = _('Repo/Image is mandatory. Cannot be left blank.') - raise exception.InvalidValue(message) - return super(ImageNameType, cls).validate(value, pattern) - - class Integer(object): type_name = 'Integer' diff --git a/zun/api/controllers/v1/containers.py b/zun/api/controllers/v1/containers.py index 704035967..8621eb3c3 100644 --- a/zun/api/controllers/v1/containers.py +++ b/zun/api/controllers/v1/containers.py @@ -67,10 +67,11 @@ class Container(base.APIBase): }, }, 'image': { - 'validate': types.ImageNameType.validate, + 'validate': types.NameType.validate, 'validate_args': { 'pattern': types.image_name_pattern }, + 'mandatory': True }, 'links': { 'validate': types.List(types.Custom(link.Link)).validate, diff --git a/zun/api/controllers/v1/images.py b/zun/api/controllers/v1/images.py index e0fc48768..2eb1c1d0f 100644 --- a/zun/api/controllers/v1/images.py +++ b/zun/api/controllers/v1/images.py @@ -48,10 +48,11 @@ class Image(base.APIBase): }, }, 'repo': { - 'validate': types.ImageNameType.validate, + 'validate': types.NameType.validate, 'validate_args': { 'pattern': types.image_name_pattern }, + 'mandatory': True }, 'tag': { 'validate': types.NameType.validate, diff --git a/zun/common/exception.py b/zun/common/exception.py index a3cf29c1f..54bc540b4 100644 --- a/zun/common/exception.py +++ b/zun/common/exception.py @@ -264,6 +264,10 @@ class InvalidValue(Invalid): message = _("Received value '%(value)s' is invalid for type %(type)s.") +class ValidationError(Invalid): + message = "%(detail)s" + + class InvalidUUID(Invalid): message = _("Expected a uuid but received %(uuid)s.") diff --git a/zun/tests/unit/api/controllers/test_types.py b/zun/tests/unit/api/controllers/test_types.py index 50d1c6fbb..95d962388 100644 --- a/zun/tests/unit/api/controllers/test_types.py +++ b/zun/tests/unit/api/controllers/test_types.py @@ -186,21 +186,6 @@ class TestTypes(test_base.BaseTestCase): value, pattern=types.container_name_pattern) - def test_image_name_type(self): - valid_image_names = ['abc', 'ABC', '123', 'a12', 'A12', 'aA1', 'a_', - 'A_', 'A-', 'a-', 'aa', 'a' * 255] - for value in valid_image_names: - self.assertEqual( - value, types.ImageNameType.validate( - value, pattern=types.image_name_pattern)) - - invalid_image_names = [None, 'a@', 'a', "", '*' * 265] - for value in invalid_image_names: - self.assertRaises(exception.InvalidValue, - types.ImageNameType.validate, - value, - pattern=types.image_name_pattern) - def test_container_memory_type(self): test_value = '4m' value = types.MemoryType.validate(test_value) diff --git a/zun/tests/unit/api/controllers/v1/test_containers.py b/zun/tests/unit/api/controllers/v1/test_containers.py index e70ed6d8f..d97a864e1 100644 --- a/zun/tests/unit/api/controllers/v1/test_containers.py +++ b/zun/tests/unit/api/controllers/v1/test_containers.py @@ -52,6 +52,19 @@ class TestContainerController(api_base.FunctionalTest): self.assertEqual(202, response.status_int) self.assertTrue(mock_container_create.called) + @patch('zun.compute.api.API.container_create') + def test_create_container_image_not_specified(self, mock_container_create): + + params = ('{"name": "MyDocker",' + '"command": "env", "memory": "512m",' + '"environment": {"key1": "val1", "key2": "val2"}}') + with self.assertRaisesRegexp(AppError, + "Required field image is missing"): + self.app.post('/v1/containers/', + params=params, + content_type='application/json') + self.assertTrue(mock_container_create.not_called) + @patch('zun.compute.api.API.container_create') def test_create_container_set_project_id_and_user_id( self, mock_container_create): diff --git a/zun/tests/unit/api/controllers/v1/test_images.py b/zun/tests/unit/api/controllers/v1/test_images.py index eb5676ae3..47267c31c 100644 --- a/zun/tests/unit/api/controllers/v1/test_images.py +++ b/zun/tests/unit/api/controllers/v1/test_images.py @@ -44,11 +44,12 @@ class TestImageController(api_base.FunctionalTest): @patch('zun.compute.api.API.image_pull') def test_image_pull_with_no_repo(self, mock_image_pull): - mock_image_pull.side_effect = lambda x, y: y - - self.assertRaises(AppError, self.app.post, '/v1/images/', + params = {} + with self.assertRaisesRegexp(AppError, + "Required field repo is missing"): + self.app.post('/v1/images/', + params=params, content_type='application/json') - self.assertTrue(mock_image_pull.not_called) @patch('zun.compute.api.API.image_pull')