From 8739529368cb755d33c3d8c532dd1c5d86f0bf85 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 3 Jun 2011 08:50:30 -0400 Subject: Implement OSAPI v1.1 style image create. --- nova/api/openstack/images.py | 11 ++++++++- nova/tests/api/openstack/fakes.py | 2 +- nova/tests/api/openstack/test_images.py | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 59d9e3082..48ea04248 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -123,7 +123,7 @@ class Controller(object): raise webob.exc.HTTPBadRequest() try: - server_id = body["image"]["serverId"] + server_id = self._server_id_from_req_data(body) image_name = body["image"]["name"] except KeyError: raise webob.exc.HTTPBadRequest() @@ -135,6 +135,9 @@ class Controller(object): """Indicates that you must use a Controller subclass.""" raise NotImplementedError + def _server_id_from_req_data(self, data): + raise NotImplementedError() + class ControllerV10(Controller): """Version 1.0 specific controller logic.""" @@ -144,6 +147,9 @@ class ControllerV10(Controller): base_url = request.application_url return images_view.ViewBuilderV10(base_url) + def _server_id_from_req_data(self, data): + return data['image']['serverId'] + class ControllerV11(Controller): """Version 1.1 specific controller logic.""" @@ -153,6 +159,9 @@ class ControllerV11(Controller): base_url = request.application_url return images_view.ViewBuilderV11(base_url) + def _server_id_from_req_data(self, data): + return data['image']['serverRef'] + def create_resource(version='1.0'): controller = { diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 17d6d591c..e9b46f933 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -143,7 +143,7 @@ def stub_out_networking(stubs): def stub_out_compute_api_snapshot(stubs): def snapshot(self, context, instance_id, name): - return 123 + return dict(id='123') stubs.Set(nova.compute.API, 'snapshot', snapshot) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 9f1f28611..961c271ca 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -249,6 +249,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): fakes.stub_out_key_pair_funcs(self.stubs) self.fixtures = self._make_image_fixtures() fakes.stub_out_glance(self.stubs, initial_fixtures=self.fixtures) + fakes.stub_out_compute_api_snapshot(self.stubs) def tearDown(self): """Run after each test.""" @@ -871,6 +872,46 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) + def test_create_image(self): + + body = dict(image=dict(serverId='123', name='Backup 1')) + req = webob.Request.blank('/v1.0/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, response.status_int) + + def test_create_image_no_server_id(self): + + body = dict(image=dict(name='Backup 1')) + req = webob.Request.blank('/v1.0/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + + def test_create_image_v1_1(self): + + body = dict(image=dict(serverRef='123', name='Backup 1')) + req = webob.Request.blank('/v1.1/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, response.status_int) + + def test_create_image_v1_1_no_server_ref(self): + + body = dict(image=dict(name='Backup 1')) + req = webob.Request.blank('/v1.1/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + @classmethod def _make_image_fixtures(cls): image_id = 123 -- cgit From 0ef4a127e9539f90ac1d2f2846832ecc48b51e05 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 3 Jun 2011 09:31:43 -0400 Subject: Add serverRef to image metadata serialization list. --- nova/api/openstack/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 48ea04248..1fa3267dc 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -177,7 +177,7 @@ def create_resource(version='1.0'): metadata = { "attributes": { "image": ["id", "name", "updated", "created", "status", - "serverId", "progress"], + "serverId", "progress", "serverRef"], "link": ["rel", "type", "href"], }, } -- cgit From eadabab8b70bdc4789615844e2263cbed7aa283c Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 3 Jun 2011 11:34:49 -0400 Subject: Added a test case for XML serialization. --- nova/tests/api/openstack/fakes.py | 3 ++- nova/tests/api/openstack/test_images.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index e9b46f933..601c1e9e4 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -143,7 +143,8 @@ def stub_out_networking(stubs): def stub_out_compute_api_snapshot(stubs): def snapshot(self, context, instance_id, name): - return dict(id='123') + return dict(id='123', status='ACTIVE', + properties=dict(instance_id='123')) stubs.Set(nova.compute.API, 'snapshot', snapshot) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 961c271ca..ae7025146 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -902,6 +902,39 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(200, response.status_int) + def test_create_image_v1_1_xml_serialization(self): + + body = dict(image=dict(serverRef='123', name='Backup 1')) + req = webob.Request.blank('/v1.1/images') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + req.headers["accept"] = "application/xml" + response = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, response.status_int) + resp_xml = minidom.parseString(response.body.replace(" ", "")) + expected_href = "http://localhost/v1.1/images/123" + expected_image = minidom.parseString(""" + + + + + + + + """.replace(" ", "") % (locals())) + + self.assertEqual(expected_image.toxml(), resp_xml.toxml()) + def test_create_image_v1_1_no_server_ref(self): body = dict(image=dict(name='Backup 1')) -- cgit From 267178748e712098af4e55872029c5883af9a51c Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 6 Jun 2011 12:42:27 -0400 Subject: Change to a more generic error and update documentation. --- nova/db/sqlalchemy/api.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 56739e9db..6dbf53a6c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -60,9 +60,7 @@ def is_user_context(context): def authorize_project_context(context, project_id): - """Ensures that the request context has permission to access the - given project. - """ + """Ensures a request has permission to access the given project.""" if is_user_context(context): if not context.project: raise exception.NotAuthorized() @@ -71,9 +69,7 @@ def authorize_project_context(context, project_id): def authorize_user_context(context, user_id): - """Ensures that the request context has permission to access the - given user. - """ + """Ensures a request has permission to access the given user.""" if is_user_context(context): if not context.user: raise exception.NotAuthorized() @@ -89,9 +85,12 @@ def can_read_deleted(context): def require_admin_context(f): - """Decorator used to indicate that the method requires an - administrator context. + """Decorator to require admin request context. + + The first argument to the wrapped function must be the context. + """ + def wrapper(*args, **kwargs): if not is_admin_context(args[0]): raise exception.AdminRequired() @@ -100,12 +99,19 @@ def require_admin_context(f): def require_context(f): - """Decorator used to indicate that the method requires either - an administrator or normal user context. + """Decorator to require *any* user or admin context. + + This does no authorization for user or project access matching, see + :py:func:`authorize_project_context` and + :py:func:`authorize_user_context`. + + The first argument to the wrapped function must be the context. + """ + def wrapper(*args, **kwargs): if not is_admin_context(args[0]) and not is_user_context(args[0]): - raise exception.AdminRequired() + raise exception.NotAuthorized() return f(*args, **kwargs) return wrapper -- cgit