From b68ab98d6718d5a7237f5620e8caffc770dfe822 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 21 Sep 2010 19:24:19 -0400 Subject: User updatable name & description for images. --- nova/endpoint/cloud.py | 12 ++++++++++++ nova/endpoint/images.py | 6 ++++++ nova/objectstore/handler.py | 19 +++++++++++++++---- nova/objectstore/image.py | 12 ++++++++++++ nova/tests/cloud_unittest.py | 26 +++++++++++++++++++++++++- nova/tests/objectstore_unittest.py | 6 ++++++ 6 files changed, 76 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index e29d09a89..b1678ecce 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -752,3 +752,15 @@ class CloudController(object): raise exception.ApiError('operation_type must be add or remove') result = images.modify(context, image_id, operation_type) return defer.succeed(result) + + @rbac.allow('projectmanager', 'sysadmin') + def set_image_name(self, context, image_id, name): + result = images.update_user_editable_field(context, image_id, + 'displayName', name) + return defer.succeed(result) + + @rbac.allow('projectmanager', 'sysadmin') + def set_image_description(self, context, image_id, name): + result = images.update_user_editable_field(context, image_id, + 'displayDescription', name) + return defer.succeed(result) diff --git a/nova/endpoint/images.py b/nova/endpoint/images.py index 4579cd81a..a63c059bb 100644 --- a/nova/endpoint/images.py +++ b/nova/endpoint/images.py @@ -43,6 +43,12 @@ def modify(context, image_id, operation): return True +def update_user_editable_field(context, image_id, field, value): + conn(context).make_request( + method='POST', + bucket='_images', + query_args=qs({'image_id': image_id, 'field': field, 'value': value})) + return True def register(context, image_location): """ rpc call to register a new image based from a manifest """ diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index 5c3dc286b..f4596e982 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -382,15 +382,26 @@ class ImagesResource(resource.Resource): def render_POST(self, request): # pylint: disable-msg=R0201 """Update image attributes: public/private""" + # image_id required for all requests image_id = get_argument(request, 'image_id', u'') - operation = get_argument(request, 'operation', u'') - image_object = image.Image(image_id) - if not image_object.is_authorized(request.context): + logging.debug("not authorized for handle_POST in images") raise exception.NotAuthorized - image_object.set_public(operation=='add') + operation = get_argument(request, 'operation', u'') + field = get_argument(request, 'field', u'') + value = get_argument(request, 'value', u'') + if operation: + # operation implies publicity toggle + logging.debug("handling publicity toggle") + image_object.set_public(operation=='add') + elif field: + # field implies user field editing (value can be blank) + logging.debug("update user field") + image_object.update_user_editable_field(field, value) + else: + logging.debug("unknown action for handle_POST in images") return '' diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index f3c02a425..ad1745af6 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -82,6 +82,18 @@ class Image(object): with open(os.path.join(self.path, 'info.json'), 'w') as f: json.dump(md, f) + def update_user_editable_field(self, field, value): + fields = ['displayName', 'displayDescription'] + if field not in fields: + raise KeyError("Invalid field: %s" % field) + info = self.metadata + if value: + info[field] = value + elif field in info: + del info[field] + with open(os.path.join(self.path, 'info.json'), 'w') as f: + json.dump(info, f) + @staticmethod def all(): images = [] diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index c36d5a34f..2e97180be 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -16,9 +16,13 @@ # License for the specific language governing permissions and limitations # under the License. +import json import logging +import os import StringIO +import tempfile import time + from tornado import ioloop from twisted.internet import defer import unittest @@ -32,15 +36,22 @@ from nova.auth import manager from nova.compute import power_state from nova.endpoint import api from nova.endpoint import cloud +from nova.objectstore import image FLAGS = flags.FLAGS +# Temp dirs for working with image attributes through the cloud controller +# (stole this from objectstore_unittest.py) +OSS_TEMPDIR = tempfile.mkdtemp(prefix='test_oss-') +IMAGES_PATH = os.path.join(OSS_TEMPDIR, 'images') +os.makedirs(IMAGES_PATH) + class CloudTestCase(test.BaseTestCase): def setUp(self): super(CloudTestCase, self).setUp() - self.flags(connection_type='fake') + self.flags(connection_type='fake', images_path=IMAGES_PATH) self.conn = rpc.Connection.instance() logging.getLogger().setLevel(logging.DEBUG) @@ -156,3 +167,16 @@ class CloudTestCase(test.BaseTestCase): #for i in xrange(4): # data = self.cloud.get_metadata(instance(i)['private_dns_name']) # self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i) + + def test_user_editable_endpoint(self): + pathdir = os.path.join(FLAGS.images_path, 'i-testing') + os.mkdir(pathdir) + info = {} + with open(os.path.join(pathdir, 'info.json'), 'w') as f: + json.dump(info, f) + yield self.cloud.set_image_description(self.context, 'i-testing', + 'Foo Img') + img = image.Image('i-testing') + self.assertEqual('Foo Img', img.metadata['displayDescription']) + self.cloud.set_image_description(self.context, 'i-testing', '') + self.assert_(not 'displayDescription' in img.metadata) diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py index dece4b5d5..82c6e2c49 100644 --- a/nova/tests/objectstore_unittest.py +++ b/nova/tests/objectstore_unittest.py @@ -164,6 +164,12 @@ class ObjectStoreTestCase(test.BaseTestCase): self.context.project = self.auth_manager.get_project('proj2') self.assertFalse(my_img.is_authorized(self.context)) + # change user-editable fields + my_img.update_user_editable_field('displayName', 'my cool image') + self.assertEqual('my cool image', my_img.metadata['displayName']) + my_img.update_user_editable_field('displayName', '') + self.assert_(not 'displayName' in my_img.metadata) + class TestHTTPChannel(http.HTTPChannel): """Dummy site required for twisted.web""" -- cgit