summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTodd Willey <todd@ansolabs.com>2010-09-21 19:24:19 -0400
committerTodd Willey <todd@ansolabs.com>2010-09-21 19:24:19 -0400
commitb68ab98d6718d5a7237f5620e8caffc770dfe822 (patch)
tree0238a693e4ea494684fbcc07014672f9814628e2
parente432c89a8cec8097bd6208cb299569698e24f639 (diff)
User updatable name & description for images.
-rw-r--r--nova/endpoint/cloud.py12
-rw-r--r--nova/endpoint/images.py6
-rw-r--r--nova/objectstore/handler.py19
-rw-r--r--nova/objectstore/image.py12
-rw-r--r--nova/tests/cloud_unittest.py26
-rw-r--r--nova/tests/objectstore_unittest.py6
6 files changed, 76 insertions, 5 deletions
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"""