From a56a973e9d839df5bcd956126300afd7df4c2fe9 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 15 Mar 2011 00:37:13 +0000 Subject: Fixing API per spec, to get unit-tests to pass --- nova/api/openstack/images.py | 118 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 98f0dd96b..7b3800429 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -15,10 +15,13 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime + from webob import exc from nova import compute from nova import flags +from nova import log from nova import utils from nova import wsgi import nova.api.openstack @@ -27,6 +30,8 @@ from nova.api.openstack import faults import nova.image.service +LOG = log.getLogger('nova.api.openstack.images') + FLAGS = flags.FLAGS @@ -84,8 +89,6 @@ def _translate_status(item): # S3ImageService pass - return item - def _filter_keys(item, keys): """ @@ -104,6 +107,89 @@ def _convert_image_id_to_hash(image): image['id'] = image_id +def _translate_s3_like_images(image_metadata): + """Work-around for leaky S3ImageService abstraction""" + api_metadata = image_metadata.copy() + _convert_image_id_to_hash(api_metadata) + api_metadata = _translate_keys(api_metadata) + _translate_status(api_metadata) + return api_metadata + + +def _translate_metadata_for_api_detail(image_metadata): + """Translate from ImageService to OpenStack API style attribute names + + This involves 3 steps: + + 1. Translating required keys + + 2. Translating optional keys (ex. progress, serverId) + + 3. Formatting values according to API spec (for example dates must + look like "2010-08-10T12:00:00Z") + """ + service_metadata = image_metadata.copy() + api_metadata = {} + + # 1. Translate required keys + required_image_service2api = { + 'id': 'id', + 'name': 'name', + 'updated_at': 'updated', + 'created_at': 'created', + 'status': 'status'} + for service_attr, api_attr in required_image_service2api.items(): + api_metadata[api_attr] = service_metadata[service_attr] + + # 2. Translate optional keys + optional_image_service2api = {'instance_id': 'serverId'} + for service_attr, api_attr in optional_image_service2api.items(): + if service_attr in service_metadata: + api_metadata[api_attr] = service_metadata[service_attr] + + # 2a. Progress special case + # TODO(sirp): ImageService doesn't have a notion of progress yet, so for + # now just fake it + if service_metadata['status'] == 'saving': + api_metadata['progress'] = 0 + + # 3. Format values + + # 3a. Format Image Status (API requires uppercase) + status_service2api = {'queued': 'QUEUED', + 'preparing': 'PREPARING', + 'saving': 'SAVING', + 'active': 'ACTIVE', + 'killed': 'FAILED'} + api_metadata['status'] = status_service2api[api_metadata['status']] + + # 3b. Format timestamps + def _format_timestamp(dt_str): + """Return a timestamp formatted for OpenStack API + + NOTE(sirp): + + ImageService (specifically GlanceImageService) is currently + returning timestamps as strings. This should probably be datetime + objects. In the mean time, we work around this by using strptime() to + create datetime objects. + """ + if dt_str is None: + return None + + service_timestamp_fmt = "%Y-%m-%dT%H:%M:%S" + api_timestamp_fmt = "%Y-%m-%dT%H:%M:%SZ" + dt = datetime.datetime.strptime(dt_str, service_timestamp_fmt) + return dt.strftime(api_timestamp_fmt) + + for ts_attr in ('created', 'updated'): + if ts_attr in api_metadata: + formatted_timestamp = _format_timestamp(api_metadata[ts_attr]) + api_metadata[ts_attr] = formatted_timestamp + + return api_metadata + + class Controller(wsgi.Controller): _serialization_metadata = { @@ -125,16 +211,28 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all public images in detail""" try: - items = self._service.detail(req.environ['nova.context']) + service_image_metas = self._service.detail( + req.environ['nova.context']) except NotImplementedError: - items = self._service.index(req.environ['nova.context']) - for image in items: - _convert_image_id_to_hash(image) + service_image_metas = self._service.index( + req.environ['nova.context']) - items = common.limited(items, req) - items = [_translate_keys(item) for item in items] - items = [_translate_status(item) for item in items] - return dict(images=items) + service_image_metas = common.limited(service_image_metas, req) + + # FIXME(sirp): The S3ImageService appears to be leaking implementation + # details, including its internal attribute names, and internal + # `status` values. Working around it for now. + s3_like_image = (service_image_metas and + ('imageId' in service_image_metas[0])) + if s3_like_image: + translate = _translate_s3_like_images + else: + translate = _translate_metadata_for_api_detail + + api_image_metas = [translate(service_image_meta) + for service_image_meta in service_image_metas] + + return dict(images=api_image_metas) def show(self, req, id): """Return data about the given image id""" -- cgit From ad6f82909060cd4d1d99a1b1a9f33aa2788d8c94 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 15 Mar 2011 05:37:08 +0000 Subject: serverId returned as int per spec --- nova/api/openstack/images.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 7b3800429..0c56b5f0d 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -116,7 +116,7 @@ def _translate_s3_like_images(image_metadata): return api_metadata -def _translate_metadata_for_api_detail(image_metadata): +def _translate_from_image_service_to_api(image_metadata): """Translate from ImageService to OpenStack API style attribute names This involves 3 steps: @@ -227,7 +227,7 @@ class Controller(wsgi.Controller): if s3_like_image: translate = _translate_s3_like_images else: - translate = _translate_metadata_for_api_detail + translate = _translate_from_image_service_to_api api_image_metas = [translate(service_image_meta) for service_image_meta in service_image_metas] -- cgit From 789fcb46915dce5fa533357ac462040ec6aa8968 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 22 Mar 2011 20:26:45 +0000 Subject: Adding BASE_IMAGE_ATTRS to ImageService --- nova/api/openstack/images.py | 57 +++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 27 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 0c56b5f0d..97e62c22d 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -119,43 +119,46 @@ def _translate_s3_like_images(image_metadata): def _translate_from_image_service_to_api(image_metadata): """Translate from ImageService to OpenStack API style attribute names - This involves 3 steps: + This involves 4 steps: - 1. Translating required keys + 1. Filter out attributes that the OpenStack API doesn't need - 2. Translating optional keys (ex. progress, serverId) + 2. Translate from base image attributes from names used by + BaseImageService to names used by OpenStack API - 3. Formatting values according to API spec (for example dates must + 3. Add in any image properties + + 4. Format values according to API spec (for example dates must look like "2010-08-10T12:00:00Z") """ service_metadata = image_metadata.copy() - api_metadata = {} - - # 1. Translate required keys - required_image_service2api = { - 'id': 'id', - 'name': 'name', - 'updated_at': 'updated', - 'created_at': 'created', - 'status': 'status'} - for service_attr, api_attr in required_image_service2api.items(): - api_metadata[api_attr] = service_metadata[service_attr] - - # 2. Translate optional keys - optional_image_service2api = {'instance_id': 'serverId'} - for service_attr, api_attr in optional_image_service2api.items(): - if service_attr in service_metadata: - api_metadata[api_attr] = service_metadata[service_attr] - - # 2a. Progress special case + properties = service_metadata.pop('properties', {}) + + # 1. Filter out unecessary attributes + api_keys = ['id', 'name', 'updated_at', 'created_at', 'status'] + api_metadata = utils.partition_dict(service_metadata, api_keys)[0] + + # 2. Translate base image attributes + api_map = {'updated_at': 'updated', 'created_at': 'created'} + api_metadata = utils.map_dict_keys(api_metadata, api_map) + + # 3. Add in any image properties + # 3a. serverId is used for backups and snapshots + try: + api_metadata['serverId'] = int(properties['instance_id']) + except KeyError: + pass # skip if it's not present + except ValueError: + pass # skip if it's not an integer + + # 3b. Progress special case # TODO(sirp): ImageService doesn't have a notion of progress yet, so for # now just fake it if service_metadata['status'] == 'saving': api_metadata['progress'] = 0 - # 3. Format values - - # 3a. Format Image Status (API requires uppercase) + # 4. Format values + # 4a. Format Image Status (API requires uppercase) status_service2api = {'queued': 'QUEUED', 'preparing': 'PREPARING', 'saving': 'SAVING', @@ -163,7 +166,7 @@ def _translate_from_image_service_to_api(image_metadata): 'killed': 'FAILED'} api_metadata['status'] = status_service2api[api_metadata['status']] - # 3b. Format timestamps + # 4b. Format timestamps def _format_timestamp(dt_str): """Return a timestamp formatted for OpenStack API -- cgit From 7d97f700811468303c21159454db64e84f71a2a0 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 22 Mar 2011 23:49:24 +0000 Subject: Refactored out _safe_translate code --- nova/api/openstack/images.py | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 97e62c22d..2d2f67fe1 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -193,6 +193,21 @@ def _translate_from_image_service_to_api(image_metadata): return api_metadata +def _safe_translate(image_metadata): + """Translate attributes for OpenStack API, temporary workaround for + S3ImageService attribute leakage. + """ + # FIXME(sirp): The S3ImageService appears to be leaking implementation + # details, including its internal attribute names, and internal + # `status` values. Working around it for now. + s3_like_image = ('imageId' in image_metadata) + if s3_like_image: + translate = _translate_s3_like_images + else: + translate = _translate_from_image_service_to_api + return translate(image_metadata) + + class Controller(wsgi.Controller): _serialization_metadata = { @@ -221,30 +236,18 @@ class Controller(wsgi.Controller): req.environ['nova.context']) service_image_metas = common.limited(service_image_metas, req) - - # FIXME(sirp): The S3ImageService appears to be leaking implementation - # details, including its internal attribute names, and internal - # `status` values. Working around it for now. - s3_like_image = (service_image_metas and - ('imageId' in service_image_metas[0])) - if s3_like_image: - translate = _translate_s3_like_images - else: - translate = _translate_from_image_service_to_api - - api_image_metas = [translate(service_image_meta) + api_image_metas = [_safe_translate(service_image_meta) for service_image_meta in service_image_metas] - return dict(images=api_image_metas) def show(self, req, id): """Return data about the given image id""" image_id = common.get_image_id_from_image_hash(self._service, req.environ['nova.context'], id) - - image = self._service.show(req.environ['nova.context'], image_id) - _convert_image_id_to_hash(image) - return dict(image=image) + service_image_meta = self._service.show( + req.environ['nova.context'], image_id) + api_image_meta = _safe_translate(service_image_meta) + return dict(image=api_image_meta) def delete(self, req, id): # Only public images are supported for now. @@ -255,11 +258,10 @@ class Controller(wsgi.Controller): env = self._deserialize(req.body, req.get_content_type()) instance_id = env["image"]["serverId"] name = env["image"]["name"] - - image_meta = compute.API().snapshot( + service_image_meta = compute.API().snapshot( context, instance_id, name) - - return dict(image=image_meta) + api_image_meta = _safe_translate(service_image_meta) + return dict(image=api_image_meta) def update(self, req, id): # Users may not modify public images, and that's all that -- cgit From ac475d05e6807804a74bca665563c7260523a733 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 23 Mar 2011 06:00:04 +0000 Subject: Small cleanup of openstack/images.py --- nova/api/openstack/images.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 2d2f67fe1..d914f5196 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -221,32 +221,28 @@ class Controller(wsgi.Controller): def index(self, req): """Return all public images in brief""" - items = self._service.index(req.environ['nova.context']) - items = common.limited(items, req) - items = [_filter_keys(item, ('id', 'name')) for item in items] - return dict(images=items) + context = req.environ['nova.context'] + image_metas = self._service.index(context) + image_metas = common.limited(image_metas, req) + return dict(images=image_metas) def detail(self, req): """Return all public images in detail""" - try: - service_image_metas = self._service.detail( - req.environ['nova.context']) - except NotImplementedError: - service_image_metas = self._service.index( - req.environ['nova.context']) - - service_image_metas = common.limited(service_image_metas, req) - api_image_metas = [_safe_translate(service_image_meta) - for service_image_meta in service_image_metas] + context = req.environ['nova.context'] + image_metas = self._service.detail(context) + image_metas = common.limited(image_metas, req) + api_image_metas = [_safe_translate(image_meta) + for image_meta in image_metas] return dict(images=api_image_metas) def show(self, req, id): """Return data about the given image id""" - image_id = common.get_image_id_from_image_hash(self._service, - req.environ['nova.context'], id) - service_image_meta = self._service.show( - req.environ['nova.context'], image_id) - api_image_meta = _safe_translate(service_image_meta) + context = req.environ['nova.context'] + image_id = common.get_image_id_from_image_hash( + self._service, req.environ['nova.context'], id) + + image_meta = self._service.show(context, image_id) + api_image_meta = _safe_translate(image_meta) return dict(image=api_image_meta) def delete(self, req, id): @@ -258,9 +254,9 @@ class Controller(wsgi.Controller): env = self._deserialize(req.body, req.get_content_type()) instance_id = env["image"]["serverId"] name = env["image"]["name"] - service_image_meta = compute.API().snapshot( + image_meta = compute.API().snapshot( context, instance_id, name) - api_image_meta = _safe_translate(service_image_meta) + api_image_meta = _safe_translate(image_meta) return dict(image=api_image_meta) def update(self, req, id): -- cgit From 32e1c38ef9539be6f914adc69f30e409b159a9e6 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 23 Mar 2011 06:55:28 +0000 Subject: Adding tests for owned and non-existent images --- nova/api/openstack/images.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d914f5196..ab286bb45 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -20,6 +20,7 @@ import datetime from webob import exc from nova import compute +from nova import exception from nova import flags from nova import log from nova import utils @@ -238,8 +239,11 @@ class Controller(wsgi.Controller): def show(self, req, id): """Return data about the given image id""" context = req.environ['nova.context'] - image_id = common.get_image_id_from_image_hash( - self._service, req.environ['nova.context'], id) + try: + image_id = common.get_image_id_from_image_hash( + self._service, context, id) + except exception.NotFound: + raise faults.Fault(exc.HTTPNotFound()) image_meta = self._service.show(context, image_id) api_image_meta = _safe_translate(image_meta) -- cgit From 07af0c9653863575600986158b89ff6afa48996e Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 23 Mar 2011 07:30:01 +0000 Subject: Use subset_dict --- nova/api/openstack/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index ab286bb45..19edcf194 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -137,7 +137,7 @@ def _translate_from_image_service_to_api(image_metadata): # 1. Filter out unecessary attributes api_keys = ['id', 'name', 'updated_at', 'created_at', 'status'] - api_metadata = utils.partition_dict(service_metadata, api_keys)[0] + api_metadata = utils.subset_dict(service_metadata, api_keys) # 2. Translate base image attributes api_map = {'updated_at': 'updated', 'created_at': 'created'} -- cgit