diff --git a/zun/api/controllers/experimental/__init__.py b/zun/api/controllers/experimental/__init__.py index 5479ec4d6..13e9d071c 100644 --- a/zun/api/controllers/experimental/__init__.py +++ b/zun/api/controllers/experimental/__init__.py @@ -81,7 +81,8 @@ class Experimental(controllers_base.APIBase): 'zun.experimental+json')] experimental.capsules = [link.make_link('self', pecan.request.host_url, - 'capsules', ''), + 'experimental/capsules', '', + bookmark=True), link.make_link('bookmark', pecan.request.host_url, 'capsules', '', diff --git a/zun/api/controllers/experimental/capsules.py b/zun/api/controllers/experimental/capsules.py index b979abbc4..41eb5194d 100644 --- a/zun/api/controllers/experimental/capsules.py +++ b/zun/api/controllers/experimental/capsules.py @@ -86,9 +86,6 @@ class CapsuleController(base.Controller): compute_api = pecan.request.compute_api policy.enforce(context, "capsule:create", action="capsule:create") - capsule_dict['capsule_version'] = 'alpha' - capsule_dict['kind'] = 'capsule' - capsules_spec = capsule_dict['spec'] containers_spec = utils.check_capsule_template(capsules_spec) capsule_dict['uuid'] = uuidutils.generate_uuid() diff --git a/zun/tests/unit/api/controllers/auth-experimental-access.ini b/zun/tests/unit/api/controllers/auth-experimental-access.ini new file mode 100644 index 000000000..b9684fe26 --- /dev/null +++ b/zun/tests/unit/api/controllers/auth-experimental-access.ini @@ -0,0 +1,19 @@ +[pipeline:main] +pipeline = cors request_id authtoken api_experimental + +[app:api_experimental] +paste.app_factory = zun.api.app:app_factory + +[filter:authtoken] +acl_public_routes = /experimental +paste.filter_factory = zun.api.middleware.auth_token:AuthTokenMiddleware.factory + +[filter:request_id] +paste.filter_factory = oslo_middleware:RequestId.factory + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +oslo_config_project = zun +latent_allow_methods = GET, PUT, POST, DELETE +latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID +latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID diff --git a/zun/tests/unit/api/controllers/experimental/__init__.py b/zun/tests/unit/api/controllers/experimental/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/zun/tests/unit/api/controllers/experimental/test_capsules.py b/zun/tests/unit/api/controllers/experimental/test_capsules.py new file mode 100644 index 000000000..c4c14f09a --- /dev/null +++ b/zun/tests/unit/api/controllers/experimental/test_capsules.py @@ -0,0 +1,141 @@ +# Copyright 2017 Arm Limited +# +# 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 mock import patch +from webtest.app import AppError +from zun.common import exception +from zun.tests.unit.api import base as api_base + + +class TestCapsuleController(api_base.FunctionalTest): + @patch('zun.compute.api.API.capsule_create') + def test_create_capsule(self, mock_capsule_create): + params = ('{"spec": {"kind": "capsule",' + '"spec": {"containers":' + '[{"environment": {"ROOT_PASSWORD": "foo0"}, ' + '"image": "test", "labels": {"app": "web"}, ' + '"image_driver": "docker", "resources": ' + '{"allocation": {"cpu": 1, "memory": 1024}}}], ' + '"volumes": [{"name": "volume1", ' + '"image": "test", "drivers": "cinder", "volumeType": ' + '"type1", "driverOptions": "options", ' + '"size": "5GB"}]}, ' + '"metadata": {"labels": [{"foo0": "bar0"}, ' + '{"foo1": "bar1"}], ' + '"name": "capsule-example"}}}') + response = self.app.post('/capsules/', + params=params, + content_type='application/json') + return_value = response.json + expected_meta_name = "capsule-example" + expected_meta_label = [{"foo0": "bar0"}, {"foo1": "bar1"}] + expected_container_num = 2 + self.assertEqual(len(return_value["containers_uuids"]), + expected_container_num) + self.assertEqual(return_value["meta_name"], expected_meta_name) + self.assertEqual(return_value["meta_labels"], expected_meta_label) + self.assertEqual(202, response.status_int) + self.assertTrue(mock_capsule_create.called) + + @patch('zun.compute.api.API.capsule_create') + def test_create_capsule_two_containers(self, mock_capsule_create): + params = ('{"spec": {"kind": "capsule",' + '"spec": {"containers":' + '[{"environment": {"ROOT_PASSWORD": "foo0"}, ' + '"image": "test1", "labels": {"app0": "web0"}, ' + '"image_driver": "docker", "resources": ' + '{"allocation": {"cpu": 1, "memory": 1024}}}, ' + '{"environment": {"ROOT_PASSWORD": "foo1"}, ' + '"image": "test1", "labels": {"app1": "web1"}, ' + '"image_driver": "docker", "resources": ' + '{"allocation": {"cpu": 1, "memory": 1024}}}]}, ' + '"metadata": {"labels": [{"foo0": "bar0"}, ' + '{"foo1": "bar1"}], ' + '"name": "capsule-example"}}}') + response = self.app.post('/capsules/', + params=params, + content_type='application/json') + return_value = response.json + expected_meta_name = "capsule-example" + expected_meta_label = [{"foo0": "bar0"}, {"foo1": "bar1"}] + expected_container_num = 3 + self.assertEqual(len(return_value["containers_uuids"]), + expected_container_num) + self.assertEqual(return_value["meta_name"], + expected_meta_name) + self.assertEqual(return_value["meta_labels"], + expected_meta_label) + self.assertEqual(202, response.status_int) + self.assertTrue(mock_capsule_create.called) + + @patch('zun.compute.api.API.capsule_create') + @patch('zun.common.utils.check_capsule_template') + def test_create_capsule_wrong_kind_set(self, mock_check_template, + mock_capsule_create): + params = ('{"spec": {"kind": "test",' + '"spec": {"containers":' + '[{"environment": {"ROOT_PASSWORD": "foo0"}, ' + '"image": "test1", "labels": {"app0": "web0"}, ' + '"image_driver": "docker", "resources": ' + '{"allocation": {"cpu": 1, "memory": 1024}}}]}, ' + '"metadata": {"labels": [{"foo0": "bar0"}], ' + '"name": "capsule-example"}}}') + mock_check_template.side_effect = exception.InvalidCapsuleTemplate( + "kind fields need to be set as capsule or Capsule") + response = self.post_json('/capsules/', params, expect_errors=True) + self.assertEqual(400, response.status_int) + self.assertFalse(mock_capsule_create.called) + + @patch('zun.compute.api.API.capsule_create') + @patch('zun.common.utils.check_capsule_template') + def test_create_capsule_less_than_one_container(self, mock_check_template, + mock_capsule_create): + params = ('{"spec": {"kind": "capsule",' + '"spec": {container:[]}, ' + '"metadata": {"labels": [{"foo0": "bar0"}], ' + '"name": "capsule-example"}}}') + mock_check_template.side_effect = exception.InvalidCapsuleTemplate( + "Capsule need to have one container at least") + response = self.post_json('/capsules/', params, expect_errors=True) + self.assertEqual(400, response.status_int) + self.assertFalse(mock_capsule_create.called) + + @patch('zun.compute.api.API.capsule_create') + @patch('zun.common.utils.check_capsule_template') + def test_create_capsule_no_container_field(self, mock_check_template, + mock_capsule_create): + params = ('{"spec": {"kind": "capsule",' + '"spec": {}, ' + '"metadata": {"labels": [{"foo0": "bar0"}], ' + '"name": "capsule-example"}}}') + mock_check_template.side_effect = exception.InvalidCapsuleTemplate( + "Capsule need to have one container at least") + self.assertRaises(AppError, self.app.post, '/capsules/', + params=params, content_type='application/json') + self.assertFalse(mock_capsule_create.called) + + @patch('zun.compute.api.API.capsule_create') + @patch('zun.common.utils.check_capsule_template') + def test_create_capsule_no_container_image(self, mock_check_template, + mock_capsule_create): + params = ('{"spec": {"kind": "capsule",' + '"spec": {container:[{"environment": ' + '{"ROOT_PASSWORD": "foo1"}]}, ' + '"metadata": {"labels": [{"foo0": "bar0"}], ' + '"name": "capsule-example"}}}') + mock_check_template.side_effect = exception.InvalidCapsuleTemplate( + "Container image is needed") + self.assertRaises(AppError, self.app.post, '/capsules/', + params=params, content_type='application/json') + self.assertFalse(mock_capsule_create.called) diff --git a/zun/tests/unit/api/controllers/test_root.py b/zun/tests/unit/api/controllers/test_root.py index c42309a8a..a043aff00 100644 --- a/zun/tests/unit/api/controllers/test_root.py +++ b/zun/tests/unit/api/controllers/test_root.py @@ -65,6 +65,22 @@ class TestRootController(api_base.FunctionalTest): {'href': 'http://localhost/images/', 'rel': 'bookmark'}]} + self.experimental_expected = { + 'media_types': + [{'base': 'application/json', + 'type': 'application/vnd.openstack.zun.experimental+json'}], + 'links': [{'href': 'http://localhost/experimental/', + 'rel': 'self'}, + {'href': + 'https://docs.openstack.org/developer' + '/zun/dev/api-spec-v1.html', + 'type': 'text/html', 'rel': 'describedby'}], + 'id': 'experimental', + 'capsules': [{'href': 'http://localhost/experimental/capsules/', + 'rel': 'self'}, + {'href': 'http://localhost/capsules/', + 'rel': 'bookmark'}]} + def make_app(self, paste_file): file_name = self.get_path(paste_file) cfg.CONF.set_override("api_paste_config", file_name, group="api") @@ -78,6 +94,10 @@ class TestRootController(api_base.FunctionalTest): response = self.app.get('/v1/') self.assertEqual(self.v1_expected, response.json) + def test_experimental_controller(self): + response = self.app.get('/experimental/') + self.assertEqual(self.experimental_expected, response.json) + def test_get_not_found(self): response = self.app.get('/a/bogus/url', expect_errors=True) assert response.status_int == 404 @@ -134,3 +154,14 @@ class TestRootController(api_base.FunctionalTest): response = app.get('/v1/containers', expect_errors=True) self.assertEqual(401, response.status_int) + + def test_auth_with_experimental_access(self): + paste_file = \ + "zun/tests/unit/api/controllers/auth-experimental-access.ini" + app = self.make_app(paste_file) + + response = app.get('/', expect_errors=True) + self.assertEqual(401, response.status_int) + + response = app.get('/experimental/') + self.assertEqual(self.experimental_expected, response.json) diff --git a/zun/tests/unit/common/test_utils.py b/zun/tests/unit/common/test_utils.py index 9f856aa22..9f2bd5d47 100644 --- a/zun/tests/unit/common/test_utils.py +++ b/zun/tests/unit/common/test_utils.py @@ -137,3 +137,41 @@ class TestUtils(base.TestCase): security_groups = ["not_attached_security_group_name"] self.assertRaises(exception.ZunException, utils.get_security_group_ids, self.context, security_groups) + + def test_check_capsule_template(self): + with self.assertRaisesRegex( + exception.InvalidCapsuleTemplate, "kind fields need to " + "be set as capsule or Capsule"): + params = ({"kind": "test", "spec": {"containers": []}}) + utils.check_capsule_template(params) + + with self.assertRaisesRegex( + exception.InvalidCapsuleTemplate, "No Spec found"): + params = ({"kind": "capsule"}) + utils.check_capsule_template(params) + + with self.assertRaisesRegex( + exception.InvalidCapsuleTemplate, + "No valid containers field"): + params = ({"kind": "capsule", "spec": {}}) + utils.check_capsule_template(params) + + with self.assertRaisesRegex( + exception.InvalidCapsuleTemplate, + "Capsule need to have one container at least"): + params = ({"kind": "capsule", "spec": {"containers": []}}) + utils.check_capsule_template(params) + + with self.assertRaisesRegex( + exception.InvalidCapsuleTemplate, "Container " + "image is needed"): + params = ({"kind": "capsule", + "spec": {"containers": [{"labels": {"app": "web"}}]}}) + utils.check_capsule_template(params) + + with self.assertRaisesRegex( + exception.InvalidCapsuleTemplate, "Container image is needed"): + params = ({"kind": "capsule", + "spec": {"containers": [{"image": "test1"}, + {"environment": {"ROOT_PASSWORD": "foo0"}}]}}) + utils.check_capsule_template(params)