From 639717b1eadb769f1d77a4ddcdb6618da4defbea Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 30 Jun 2011 10:06:06 -0400 Subject: added FlavorRef exception handling on create instance --- nova/api/openstack/create_instance_helper.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 436e524c1..94ae3bb85 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -143,6 +143,10 @@ class CreateInstanceHelper(object): except exception.ImageNotFound as error: msg = _("Can not find requested image") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + except exception.FlavorNotFound as error: + msg = _("Invalid flavorRef provided.") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + # Let the caller deal with unhandled exceptions. -- cgit From 9e7c7706a76ad76612ba75314d436a8ba419a3eb Mon Sep 17 00:00:00 2001 From: Mohammed Naser Date: Sat, 9 Jul 2011 21:45:54 -0400 Subject: Fixed two typos in rescue API command --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ee263f614..0e2fe128d 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1037,7 +1037,7 @@ class CloudController(object): def rescue_instance(self, context, instance_id, **kwargs): """This is an extension to the normal ec2_api""" - self._do_instance(self.compute_api.rescue, contect, instnace_id) + self._do_instance(self.compute_api.rescue, context, instance_id) return True def unrescue_instance(self, context, instance_id, **kwargs): -- cgit From 54be28647ac3ad401006bca3069b1dfc1a65d093 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 12 Jul 2011 14:35:09 -0400 Subject: server create deserialization functional and tested --- nova/api/openstack/create_instance_helper.py | 81 ++++++++++++++++++++-------- nova/api/openstack/servers.py | 63 ++++++++++++++++++++-- nova/api/openstack/wsgi.py | 21 ++++++++ 3 files changed, 139 insertions(+), 26 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2654e3c40..eea973a56 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -180,7 +180,7 @@ class CreateInstanceHelper(object): Overrides normal behavior in the case of xml content """ if request.content_type == "application/xml": - deserializer = ServerCreateRequestXMLDeserializer() + deserializer = ServerXMLDeserializer() return deserializer.deserialize(request.body) else: return self._deserialize(request.body, request.get_content_type()) @@ -295,9 +295,15 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): """Marshal the server attribute of a parsed request""" server = {} server_node = self._find_first_child_named(node, 'server') - for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]: + for attr in ["name", "imageId", "flavorId"]: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) + image = self._extract_image(server_node) + if image is not None: + server["image"] = image + flavor = self._extract_flavor(server_node) + if flavor is not None: + server["flavor"] = flavor metadata = self._extract_metadata(server_node) if metadata is not None: server["metadata"] = metadata @@ -306,6 +312,56 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): server["personality"] = personality return server + def _extract_image(self, server_node): + """Retrieve an image entity from the server node""" + image_node = self._find_first_child_named(server_node, "image") + if image_node is None: + return None + + image = {} + image_id = image_node.getAttribute('id') + if image_id: + image['id'] = image_id + + image_links = self._extract_links_from_node(image_node) + if len(image_links) > 0: + image['links'] = image_links + + return image + + def _extract_flavor(self, server_node): + """Retrieve a flavor entity from the server node""" + flavor_node = self._find_first_child_named(server_node, "flavor") + if flavor_node is None: + return None + + flavor = {} + flavor_id = flavor_node.getAttribute('id') + if flavor_id: + flavor['id'] = flavor_id + + flavor_links = self._extract_links_from_node(flavor_node) + if len(flavor_links) > 0: + flavor['links'] = flavor_links + + return flavor + + def _extract_links_from_node(self, parent_node): + """Retrieve link entities from a links container provided node""" + links = [] + + for link_node in self._find_children_named(parent_node, 'atom:link'): + link = {} + link_rel = link_node.getAttribute('rel') + if link_rel is not None: + link['rel'] = link_rel + link_href = link_node.getAttribute('href') + if link_href is not None: + link['href'] = link_href + links.append(link) + + return links + def _extract_metadata(self, server_node): """Marshal the metadata attribute of a parsed request""" metadata_node = self._find_first_child_named(server_node, "metadata") @@ -331,24 +387,3 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): item["contents"] = self._extract_text(file_node) personality.append(item) return personality - - def _find_first_child_named(self, parent, name): - """Search a nodes children for the first child with a given name""" - for node in parent.childNodes: - if node.nodeName == name: - return node - return None - - def _find_children_named(self, parent, name): - """Return all of a nodes children who have the given name""" - for node in parent.childNodes: - if node.nodeName == name: - yield node - - def _extract_text(self, node): - """Get the text field contained by the given node""" - if len(node.childNodes) == 1: - child = node.childNodes[0] - if child.nodeType == child.TEXT_NODE: - return child.nodeValue - return "" diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 12af44a8d..f239044ff 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -492,11 +492,68 @@ class ControllerV11(Controller): return faults.Fault(exc.HTTPNotFound()) def _image_ref_from_req_data(self, data): - return data['server']['imageRef'] + try: + image = data['server']['image'] + except (AttributeError, KeyError): + msg = _("Missing image entity") + raise exc.HTTPBadRequest(explanation=msg) + + try: + links = image.get('links', []) + except AttributeError: + msg = _("Malformed image entity") + raise exc.HTTPBadRequest(explanation=msg) + + image_ref = None + for link in links: + try: + if link.get('rel') == 'bookmark': + image_ref = link.get('href') + break + except AttributeError: + msg = _("Malformed image link") + raise exc.HTTPBadRequest(explanation=msg) + + if image_ref is None: + try: + image_ref = image['id'] + except KeyError: + msg = _("Missing id attribute on image entity") + raise exc.HTTPBadRequest(explanation=msg) + + return image_ref def _flavor_id_from_req_data(self, data): - href = data['server']['flavorRef'] - return common.get_id_from_href(href) + try: + flavor = data['server']['flavor'] + except (AttributeError, KeyError): + msg = _("Missing flavor entity") + raise exc.HTTPBadRequest(explanation=msg) + + try: + links = flavor.get('links', []) + except AttributeError: + msg = _("Malformed flavor entity") + raise exc.HTTPBadRequest(explanation=msg) + + flavor_ref = None + for link in links: + try: + if link.get('rel') == 'bookmark': + flavor_ref = link.get('href') + break + except AttributeError: + msg = _("Malformed flavor link") + raise exc.HTTPBadRequest(explanation=msg) + + if flavor_ref is None: + try: + return flavor['id'] + except (KeyError, AttributeError): + msg = _("Missing id attribute in flavor entity") + raise exc.HTTPBadRequest(explanation=msg) + else: + return common.get_id_from_href(flavor_ref) def _get_view_builder(self, req): base_url = req.application_url diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 8eff9e441..0ece2cff0 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -134,6 +134,27 @@ class XMLDeserializer(TextDeserializer): listnames) return result + def _find_first_child_named(self, parent, name): + """Search a nodes children for the first child with a given name""" + for node in parent.childNodes: + if node.nodeName == name: + return node + return None + + def _find_children_named(self, parent, name): + """Return all of a nodes children who have the given name""" + for node in parent.childNodes: + if node.nodeName == name: + yield node + + def _extract_text(self, node): + """Get the text field contained by the given node""" + if len(node.childNodes) == 1: + child = node.childNodes[0] + if child.nodeType == child.TEXT_NODE: + return child.nodeValue + return "" + def default(self, datastring): return {'body': self._from_xml(datastring)} -- cgit From 07baabb67d9491da61fa5bfe9adc52f7ff744e22 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 12 Jul 2011 16:02:39 -0400 Subject: cleanup --- nova/api/openstack/create_instance_helper.py | 24 ++++++---------- nova/api/openstack/servers.py | 41 ++++++++++++---------------- 2 files changed, 27 insertions(+), 38 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index eea973a56..e46bc9d98 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -320,12 +320,10 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): image = {} image_id = image_node.getAttribute('id') - if image_id: + if image_id is not None: image['id'] = image_id - image_links = self._extract_links_from_node(image_node) - if len(image_links) > 0: - image['links'] = image_links + image['links'] = self._extract_links_from_node(image_node) return image @@ -340,9 +338,7 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): if flavor_id: flavor['id'] = flavor_id - flavor_links = self._extract_links_from_node(flavor_node) - if len(flavor_links) > 0: - flavor['links'] = flavor_links + flavor['links'] = self._extract_links_from_node(flavor_node) return flavor @@ -351,14 +347,12 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): links = [] for link_node in self._find_children_named(parent_node, 'atom:link'): - link = {} - link_rel = link_node.getAttribute('rel') - if link_rel is not None: - link['rel'] = link_rel - link_href = link_node.getAttribute('href') - if link_href is not None: - link['href'] = link_href - links.append(link) + link = { + 'rel': link_node.getAttribute('rel'), + 'href': link_node.getAttribute('href'), + } + if link['rel'] is not None and link['href'] is not None: + links.append(link) return links diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f239044ff..1e8749f56 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -491,10 +491,21 @@ class ControllerV11(Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) + def _href_from_bookmark_links(self, links) + for link in links: + try: + if link.get('rel') == 'bookmark': + href = link.get('href') + if href is not None: + return href + except AttributeError: + msg = _("Malformed link entity") + raise exc.HTTPBadRequest(explanation=msg) + def _image_ref_from_req_data(self, data): try: image = data['server']['image'] - except (AttributeError, KeyError): + except (TypeError, KeyError): msg = _("Missing image entity") raise exc.HTTPBadRequest(explanation=msg) @@ -504,29 +515,21 @@ class ControllerV11(Controller): msg = _("Malformed image entity") raise exc.HTTPBadRequest(explanation=msg) - image_ref = None - for link in links: - try: - if link.get('rel') == 'bookmark': - image_ref = link.get('href') - break - except AttributeError: - msg = _("Malformed image link") - raise exc.HTTPBadRequest(explanation=msg) + image_ref = self._href_from_bookmark_links(links) if image_ref is None: try: - image_ref = image['id'] + return image['id'] except KeyError: msg = _("Missing id attribute on image entity") raise exc.HTTPBadRequest(explanation=msg) - - return image_ref + else: + return image_ref def _flavor_id_from_req_data(self, data): try: flavor = data['server']['flavor'] - except (AttributeError, KeyError): + except (TypeError, KeyError): msg = _("Missing flavor entity") raise exc.HTTPBadRequest(explanation=msg) @@ -536,15 +539,7 @@ class ControllerV11(Controller): msg = _("Malformed flavor entity") raise exc.HTTPBadRequest(explanation=msg) - flavor_ref = None - for link in links: - try: - if link.get('rel') == 'bookmark': - flavor_ref = link.get('href') - break - except AttributeError: - msg = _("Malformed flavor link") - raise exc.HTTPBadRequest(explanation=msg) + flavor_ref = self._href_from_bookmark_links(links) if flavor_ref is None: try: -- cgit From 486afc9b9e38a68c18b80daab4f23c5b936ee185 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 13 Jul 2011 11:17:05 -0400 Subject: pep8 --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1e8749f56..50d1442d4 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -491,7 +491,7 @@ class ControllerV11(Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - def _href_from_bookmark_links(self, links) + def _href_from_bookmark_links(self, links): for link in links: try: if link.get('rel') == 'bookmark': -- cgit From e1eca2190b66c745b425579affd2407e0ffd11c6 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 13 Jul 2011 15:59:05 -0400 Subject: beginning server detail spec 1.1 fixup --- nova/api/openstack/common.py | 23 +++++++++++++++++++++++ nova/api/openstack/views/servers.py | 27 ++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 79969d393..26b8c1946 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -136,6 +136,29 @@ def get_id_from_href(href): raise ValueError(_('could not parse id from href')) +def get_uuid_from_href(href): + """Return the uuid portion of a url. + + Given: 'http://www.foo.com/bar/123?q=4' + Returns: '123' + + In order to support local hrefs, the href argument can be just an id: + Given: '123' + Returns: '123' + + """ + if not '/' in href: + return href + try: + href = urlparse(href).path.split('/')[-1] + if href == '': + raise Exception + return href + except: + LOG.debug(_("Error extracting uuid from href: %s") % href) + raise ValueError(_('could not parse uuid from href')) + + def remove_version_from_href(href): """Removes the api version from the href. diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 67fb6a84e..7fb180246 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -50,7 +50,7 @@ class ViewBuilder(object): else: server = self._build_simple(inst) - self._build_extra(server, inst) + self._build_extra(server['server'], inst) return server @@ -99,7 +99,6 @@ class ViewBuilder(object): self._build_image(inst_dict, inst) self._build_flavor(inst_dict, inst) - inst_dict['uuid'] = inst['uuid'] return dict(server=inst_dict) def _build_image(self, response, inst): @@ -117,6 +116,9 @@ class ViewBuilder(object): class ViewBuilderV10(ViewBuilder): """Model an Openstack API V1.0 server response.""" + def _build_extra(self, response, inst): + response['uuid'] = inst['uuid'] + def _build_image(self, response, inst): if 'image_ref' in dict(inst): image_ref = inst['image_ref'] @@ -143,16 +145,31 @@ class ViewBuilderV11(ViewBuilder): image_href = inst['image_ref'] if str(image_href).isdigit(): image_href = int(image_href) - response['imageRef'] = image_href + response['image'] = { + "id": common.get_uuid_from_href(image_href), + "links": [ + { + "rel": "self", + "href": image_href, + }, + { + "rel": "bookmark", + "href": common.remove_version_from_href(image_href), + }, + ] + } def _build_flavor(self, response, inst): if "instance_type" in dict(inst): flavor_id = inst["instance_type"]['flavorid'] flavor_ref = self.flavor_builder.generate_href(flavor_id) - response["flavorRef"] = flavor_ref + response["flavor"] = { + "id": common.get_uuid_from_href(flavor_ref), + } def _build_extra(self, response, inst): self._build_links(response, inst) + response['id'] = inst['uuid'] def _build_links(self, response, inst): href = self.generate_href(inst["id"]) @@ -169,7 +186,7 @@ class ViewBuilderV11(ViewBuilder): }, ] - response["server"]["links"] = links + response["links"] = links def generate_href(self, server_id): """Create an url that refers to a specific server id.""" -- cgit From 38f949608fc3faaae855bf91719a074c1c545a1d Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 13 Jul 2011 16:22:55 -0400 Subject: updated to support and check for flavor links in server detail response --- nova/api/openstack/views/servers.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 7fb180246..e17edd6b6 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -163,8 +163,19 @@ class ViewBuilderV11(ViewBuilder): if "instance_type" in dict(inst): flavor_id = inst["instance_type"]['flavorid'] flavor_ref = self.flavor_builder.generate_href(flavor_id) + flavor_bookmark = self.flavor_builder.generate_bookmark(flavor_id) response["flavor"] = { "id": common.get_uuid_from_href(flavor_ref), + "links": [ + { + "rel": "self", + "href": flavor_ref, + }, + { + "rel": "bookmark", + "href": flavor_bookmark, + }, + ] } def _build_extra(self, response, inst): -- cgit From 934e8ab289cd906a94170f0d53cc49edf2db0d6f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 13 Jul 2011 19:32:17 -0400 Subject: adding new query parameters --- nova/api/openstack/images.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d0317583e..b0364b403 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -35,7 +35,13 @@ from nova.api.openstack import wsgi LOG = log.getLogger('nova.api.openstack.images') FLAGS = flags.FLAGS -SUPPORTED_FILTERS = ['name', 'status'] +SUPPORTED_FILTERS = { + 'name': 'name', + 'status': 'status', + 'changes-since': 'changes-since', + 'server': 'property-instance_ref', + 'type': 'property-image_type', +} class Controller(object): @@ -62,8 +68,9 @@ class Controller(object): filters = {} for param in req.str_params: if param in SUPPORTED_FILTERS or param.startswith('property-'): - filters[param] = req.str_params.get(param) - + # map filter name or carry through if property-* + _param = SUPPORTED_FILTERS.get(param, param) + filters[_param] = req.str_params.get(param) return filters def show(self, req, id): -- cgit From 10ef948271130db0dcc53b0ea94c3a47313244f4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 14 Jul 2011 09:29:27 -0400 Subject: removing mox object instantiation from each test; renaming _param to filter_name --- 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 b0364b403..f82b98a38 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -69,8 +69,8 @@ class Controller(object): for param in req.str_params: if param in SUPPORTED_FILTERS or param.startswith('property-'): # map filter name or carry through if property-* - _param = SUPPORTED_FILTERS.get(param, param) - filters[_param] = req.str_params.get(param) + filter_name = SUPPORTED_FILTERS.get(param, param) + filters[filter_name] = req.str_params.get(param) return filters def show(self, req, id): -- cgit From 11f05e040ad50c64e4de46dc8f8e6246956f774b Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 14 Jul 2011 10:31:42 -0400 Subject: add updated and created to servers detail test, and make it work --- nova/api/openstack/views/servers.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index e17edd6b6..5d906f3df 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -181,6 +181,8 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['id'] = inst['uuid'] + response['created'] = inst['created_at'] + response['updated'] = inst['updated_at'] def _build_links(self, response, inst): href = self.generate_href(inst["id"]) -- cgit From 3041f2e9eb90e447adbb48827c2c85ca27d436e6 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 14 Jul 2011 13:25:40 -0400 Subject: Added progress attribute to servers responses --- nova/api/openstack/views/servers.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 5d906f3df..3f13e469a 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -183,6 +183,10 @@ class ViewBuilderV11(ViewBuilder): response['id'] = inst['uuid'] response['created'] = inst['created_at'] response['updated'] = inst['updated_at'] + if response['status'] == "ACTIVE": + response['progress'] = 100 + elif response['status'] == "BUILD": + response['progress'] = 0 def _build_links(self, response, inst): href = self.generate_href(inst["id"]) -- cgit From 7044080daf2e487a92bba2a9f9b99e3cdb874e88 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 14 Jul 2011 14:01:04 -0400 Subject: updated image entity for servers requests --- nova/api/openstack/views/servers.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 3f13e469a..968a904d9 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -143,21 +143,28 @@ class ViewBuilderV11(ViewBuilder): def _build_image(self, response, inst): if 'image_ref' in dict(inst): image_href = inst['image_ref'] - if str(image_href).isdigit(): - image_href = int(image_href) - response['image'] = { - "id": common.get_uuid_from_href(image_href), - "links": [ - { - "rel": "self", - "href": image_href, - }, - { - "rel": "bookmark", - "href": common.remove_version_from_href(image_href), - }, - ] - } + #if id is a uuid do: + if image_href == common.get_uuid_from_href(image_href): + image_id = image_href + _bookmark = self.image_builder.generate_bookmark(image_href) + response['image'] = { + "id": image_id, + "links": [ + { + "rel": "bookmark", + "href": _bookmark, + }, + ] + } + else: + response['image'] = { + "links": [ + { + "rel": "bookmark", + "href": image_href, + }, + ] + } def _build_flavor(self, response, inst): if "instance_type" in dict(inst): -- cgit From 11e76f0e36f9da1840a9356b2e0a0dba87df3040 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 14 Jul 2011 14:35:11 -0400 Subject: removed self links from flavors --- nova/api/openstack/views/servers.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 968a904d9..9d6699ed0 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -174,10 +174,6 @@ class ViewBuilderV11(ViewBuilder): response["flavor"] = { "id": common.get_uuid_from_href(flavor_ref), "links": [ - { - "rel": "self", - "href": flavor_ref, - }, { "rel": "bookmark", "href": flavor_bookmark, @@ -190,10 +186,11 @@ class ViewBuilderV11(ViewBuilder): response['id'] = inst['uuid'] response['created'] = inst['created_at'] response['updated'] = inst['updated_at'] - if response['status'] == "ACTIVE": - response['progress'] = 100 - elif response['status'] == "BUILD": - response['progress'] = 0 + if 'status' in response: + if response['status'] == "ACTIVE": + response['progress'] = 100 + elif response['status'] == "BUILD": + response['progress'] = 0 def _build_links(self, response, inst): href = self.generate_href(inst["id"]) -- cgit From 6cbce0442cf15d31e7ab05b808939bdc3a244f1b Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 14 Jul 2011 17:35:10 -0400 Subject: Added ServersTestv1_1 test case Changed servers links to use uuid instead of id --- nova/api/openstack/views/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 9d6699ed0..45209d9e2 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -193,8 +193,8 @@ class ViewBuilderV11(ViewBuilder): response['progress'] = 0 def _build_links(self, response, inst): - href = self.generate_href(inst["id"]) - bookmark = self.generate_bookmark(inst["id"]) + href = self.generate_href(inst["uuid"]) + bookmark = self.generate_bookmark(inst["uuid"]) links = [ { -- cgit From a36d93372db8556b70d28cf52644da38c7f9a02f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 15 Jul 2011 10:29:06 -0400 Subject: Added ViewBuilderV11 tests Fixed bug with build detail --- nova/api/openstack/views/servers.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 45209d9e2..570488cfa 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -140,6 +140,18 @@ class ViewBuilderV11(ViewBuilder): self.image_builder = image_builder self.base_url = base_url + def _build_detail(self, inst): + response = super(ViewBuilderV11, self)._build_detail(inst) + response['server']['created'] = inst['created_at'] + response['server']['updated'] = inst['updated_at'] + if 'status' in response['server']: + if response['server']['status'] == "ACTIVE": + response['server']['progress'] = 100 + elif response['server']['status'] == "BUILD": + response['server']['progress'] = 0 + return response + + def _build_image(self, response, inst): if 'image_ref' in dict(inst): image_href = inst['image_ref'] @@ -184,13 +196,6 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['id'] = inst['uuid'] - response['created'] = inst['created_at'] - response['updated'] = inst['updated_at'] - if 'status' in response: - if response['status'] == "ACTIVE": - response['progress'] = 100 - elif response['status'] == "BUILD": - response['progress'] = 0 def _build_links(self, response, inst): href = self.generate_href(inst["uuid"]) -- cgit From 1e7b21452a2e622b94531bea59c68e6abcded40a Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 12:44:40 -0400 Subject: return id and uuid for now --- nova/api/openstack/views/servers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index a4c9dd95a..53ded4312 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -206,7 +206,8 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) - response['id'] = inst['uuid'] + response['id'] = inst['id'] + response['uuid'] = inst['uuid'] def _build_links(self, response, inst): href = self.generate_href(inst["uuid"]) -- cgit From 054f828d341b6cc576e30744a39d443af7784fd9 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 14:05:26 -0400 Subject: pep8 fixes --- nova/api/openstack/views/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 53ded4312..a1aba20fc 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -158,7 +158,6 @@ class ViewBuilderV11(ViewBuilder): response['server']['progress'] = 0 return response - def _build_image(self, response, inst): if 'image_ref' in dict(inst): image_href = inst['image_ref'] -- cgit From 1c6837c7940ed979a05a063595f4d7e7a2154ee9 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 14:43:33 -0400 Subject: use id in links instead of uuid --- nova/api/openstack/views/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index a1aba20fc..71d685171 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -209,8 +209,8 @@ class ViewBuilderV11(ViewBuilder): response['uuid'] = inst['uuid'] def _build_links(self, response, inst): - href = self.generate_href(inst["uuid"]) - bookmark = self.generate_bookmark(inst["uuid"]) + href = self.generate_href(inst["id"]) + bookmark = self.generate_bookmark(inst["id"]) links = [ { -- cgit From 63d9a592823cdb2e120514137a1d71d838b336d7 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 15:08:31 -0400 Subject: stop using get_uuid_from_href for now --- nova/api/openstack/views/servers.py | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 71d685171..a9db8357e 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -161,28 +161,18 @@ class ViewBuilderV11(ViewBuilder): def _build_image(self, response, inst): if 'image_ref' in dict(inst): image_href = inst['image_ref'] - #if id is a uuid do: - if image_href == common.get_uuid_from_href(image_href): - image_id = image_href - _bookmark = self.image_builder.generate_bookmark(image_href) - response['image'] = { - "id": image_id, - "links": [ - { - "rel": "bookmark", - "href": _bookmark, - }, - ] - } - else: - response['image'] = { - "links": [ - { - "rel": "bookmark", - "href": image_href, - }, - ] - } + image_id = str(common.get_id_from_href(image_href)) + print "IMAGE ID:", image_id + _bookmark = self.image_builder.generate_bookmark(image_id) + response['image'] = { + "id": image_id, + "links": [ + { + "rel": "bookmark", + "href": _bookmark, + }, + ] + } def _build_flavor(self, response, inst): if "instance_type" in dict(inst): @@ -190,7 +180,7 @@ class ViewBuilderV11(ViewBuilder): flavor_ref = self.flavor_builder.generate_href(flavor_id) flavor_bookmark = self.flavor_builder.generate_bookmark(flavor_id) response["flavor"] = { - "id": common.get_uuid_from_href(flavor_ref), + "id": str(common.get_id_from_href(flavor_ref)), "links": [ { "rel": "bookmark", -- cgit From 1e16da70134537ae53d0c79214e70f0f06b14dd2 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 15:10:15 -0400 Subject: remove get_uuid_from_href and tests --- nova/api/openstack/common.py | 23 ----------------------- nova/api/openstack/views/servers.py | 1 - 2 files changed, 24 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index e7aa7d693..8e12ce0c0 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -136,29 +136,6 @@ def get_id_from_href(href): raise ValueError(_('could not parse id from href')) -def get_uuid_from_href(href): - """Return the uuid portion of a url. - - Given: 'http://www.foo.com/bar/123?q=4' - Returns: '123' - - In order to support local hrefs, the href argument can be just an id: - Given: '123' - Returns: '123' - - """ - if not '/' in href: - return href - try: - href = urlparse(href).path.split('/')[-1] - if href == '': - raise Exception - return href - except: - LOG.debug(_("Error extracting uuid from href: %s") % href) - raise ValueError(_('could not parse uuid from href')) - - def remove_version_from_href(href): """Removes the first api version from the href. diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index a9db8357e..6d6ebbafd 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -195,7 +195,6 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) - response['id'] = inst['id'] response['uuid'] = inst['uuid'] def _build_links(self, response, inst): -- cgit From 910dd2124123d8e1989ce85d57bcc2a245e39683 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 16:04:41 -0400 Subject: take out print statements --- nova/api/openstack/views/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 6d6ebbafd..817e6ddfc 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -162,7 +162,6 @@ class ViewBuilderV11(ViewBuilder): if 'image_ref' in dict(inst): image_href = inst['image_ref'] image_id = str(common.get_id_from_href(image_href)) - print "IMAGE ID:", image_id _bookmark = self.image_builder.generate_bookmark(image_id) response['image'] = { "id": image_id, -- cgit From 7498fa608def9613552cf0e26dcb03fddf7b298d Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 15 Jul 2011 22:56:16 +0000 Subject: renamed priv method arg_to_dict since it's not just used for revoke. modified to conform to latest AWS EC2 API spec for authorize & revoke ingress params using the IpPermissions data structure, which nests lists of CIDR blocks (IpRanges) as well as lists of Group data --- nova/api/ec2/cloud.py | 83 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index acfd1361c..5c0c79429 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -441,7 +441,52 @@ class CloudController(object): g['ipPermissions'] += [r] return g - def _revoke_rule_args_to_dict(self, context, to_port=None, from_port=None, + def _rule_args_to_dict(self, context, kwargs): + rules = [] + if not kwargs.has_key('groups') and not kwargs.has_key('ip_ranges'): + rule = self._rule_dict_last_step(context, **kwargs) + if rule: + rules.append(rule) + return rules + if kwargs.has_key('ip_ranges'): + rules = self._cidr_args_split(kwargs) + finalset = [] + for rule in rules: + if rule.has_key('groups'): + groups_values = self._groups_args_split(rule) + for groups_value in groups_values: + finalset.append(groups_value) + else: + if rule: + finalset.append(rule) + return finalset + + def _cidr_args_split(self, kwargs): + cidr_args_split= [] + cidrs = kwargs['ip_ranges'] + for key, cidr in cidrs.iteritems(): + mykwargs = kwargs.copy() + del mykwargs['ip_ranges'] + mykwargs['cidr_ip'] = cidr['cidr_ip'] + cidr_args_split.append(mykwargs) + return cidr_args_split + + def _groups_args_split(self, kwargs): + groups_args_split= [] + groups = kwargs['groups'] + for key, group in groups.iteritems(): + mykwargs = kwargs.copy() + del mykwargs['groups'] + if group.has_key('group_name'): + mykwargs['source_security_group_name'] = group['group_name'] + if group.has_key('user_id'): + mykwargs['source_security_group_owner_id'] = group['user_id'] + if group.has_key('group_id'): + mykwargs['source_security_group_id'] = group['group_id'] + groups_args_split.append(mykwargs) + return groups_args_split + + def _rule_dict_last_step(self, context, to_port=None, from_port=None, ip_protocol=None, cidr_ip=None, user_id=None, source_security_group_name=None, source_security_group_owner_id=None): @@ -526,7 +571,7 @@ class CloudController(object): msg = "Revoke security group ingress %s" LOG.audit(_(msg), security_group['name'], context=context) - criteria = self._revoke_rule_args_to_dict(context, **kwargs) + criteria = self._rule_args_to_dict(context, kwargs)[0] if criteria is None: raise exception.ApiError(_("Not enough parameters to build a " "valid rule.")) @@ -567,21 +612,31 @@ class CloudController(object): msg = "Authorize security group ingress %s" LOG.audit(_(msg), security_group['name'], context=context) - values = self._revoke_rule_args_to_dict(context, **kwargs) - if values is None: - raise exception.ApiError(_("Not enough parameters to build a " - "valid rule.")) - values['parent_group_id'] = security_group.id - - if self._security_group_rule_exists(security_group, values): - raise exception.ApiError(_('This rule already exists in group %s') - % group_name) - - security_group_rule = db.security_group_rule_create(context, values) + prevalues = [] + try: + prevalues = kwargs['ip_permissions'] + except KeyError: + prevalues.append(kwargs) + postvalues = [] + for values in prevalues: + rulesvalues = self._rule_args_to_dict(context, values) + if not rulesvalues: + raise exception.ApiError(_("%s Not enough parameters to build a " + "valid rule." % rulesvalues)) + for values_for_rule in rulesvalues: + values_for_rule['parent_group_id'] = security_group.id + if self._security_group_rule_exists(security_group, values_for_rule): + raise exception.ApiError(_('%s - This rule already exists in group %s') + % (values_for_rule, group_name)) + postvalues.append(values_for_rule) + + for values_for_rule in postvalues: + security_group_rule = db.security_group_rule_create(context, values_for_rule) self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) + security_group_id=security_group['id']) + group = db.security_group_get_by_name(context, context.project_id, security_group['name']) return True def _get_source_project_id(self, context, source_security_group_owner_id): -- cgit From 64a9c37cbf070345831ba6e4db646c5d972e179b Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sat, 16 Jul 2011 19:17:08 -0400 Subject: Added ServerXMLSerializer with working 'show' method Factored out MetadataXMLSerializer from images and servers into common --- nova/api/openstack/common.py | 50 ++++++++++++++++++++ nova/api/openstack/image_metadata.py | 49 -------------------- nova/api/openstack/images.py | 4 +- nova/api/openstack/servers.py | 89 ++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 51 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 8e12ce0c0..8a78292aa 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -17,12 +17,14 @@ import re from urlparse import urlparse +from xml.dom import minidom import webob from nova import exception from nova import flags from nova import log as logging +from nova.api.openstack import wsgi LOG = logging.getLogger('nova.api.openstack.common') @@ -162,3 +164,51 @@ def remove_version_from_href(href): msg = _('href does not contain version') raise ValueError(msg) return new_href + + +class MetadataXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self, xmlns=wsgi.XMLNS_V11): + super(MetadataXMLSerializer, self).__init__(xmlns=xmlns) + + def _meta_item_to_xml(self, doc, key, value): + node = doc.createElement('meta') + doc.appendChild(node) + node.setAttribute('key', '%s' % key) + text = doc.createTextNode('%s' % value) + node.appendChild(text) + return node + + def meta_list_to_xml(self, xml_doc, meta_items): + container_node = xml_doc.createElement('metadata') + for (key, value) in meta_items: + item_node = self._meta_item_to_xml(xml_doc, key, value) + container_node.appendChild(item_node) + return container_node + + def _meta_list_to_xml_string(self, metadata_dict): + xml_doc = minidom.Document() + items = metadata_dict['metadata'].items() + container_node = self.meta_list_to_xml(xml_doc, items) + xml_doc.appendChild(container_node) + self._add_xmlns(container_node) + return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') + + def index(self, metadata_dict): + return self._meta_list_to_xml_string(metadata_dict) + + def create(self, metadata_dict): + return self._meta_list_to_xml_string(metadata_dict) + + def _meta_item_to_xml_string(self, meta_item_dict): + xml_doc = minidom.Document() + item_key, item_value = meta_item_dict.items()[0] + item_node = self._meta_item_to_xml(xml_doc, item_key, item_value) + xml_doc.appendChild(item_node) + self._add_xmlns(item_node) + return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') + + def show(self, meta_item_dict): + return self._meta_item_to_xml_string(meta_item_dict['meta']) + + def update(self, meta_item_dict): + return self._meta_item_to_xml_string(meta_item_dict['meta']) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 4f33844fa..dd1a3f130 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -16,7 +16,6 @@ # under the License. from webob import exc -from xml.dom import minidom from nova import flags from nova import image @@ -111,54 +110,6 @@ class Controller(object): self.image_service.update(context, image_id, img, None) -class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): - def __init__(self, xmlns=wsgi.XMLNS_V11): - super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns) - - def _meta_item_to_xml(self, doc, key, value): - node = doc.createElement('meta') - doc.appendChild(node) - node.setAttribute('key', '%s' % key) - text = doc.createTextNode('%s' % value) - node.appendChild(text) - return node - - def meta_list_to_xml(self, xml_doc, meta_items): - container_node = xml_doc.createElement('metadata') - for (key, value) in meta_items: - item_node = self._meta_item_to_xml(xml_doc, key, value) - container_node.appendChild(item_node) - return container_node - - def _meta_list_to_xml_string(self, metadata_dict): - xml_doc = minidom.Document() - items = metadata_dict['metadata'].items() - container_node = self.meta_list_to_xml(xml_doc, items) - xml_doc.appendChild(container_node) - self._add_xmlns(container_node) - return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') - - def index(self, metadata_dict): - return self._meta_list_to_xml_string(metadata_dict) - - def create(self, metadata_dict): - return self._meta_list_to_xml_string(metadata_dict) - - def _meta_item_to_xml_string(self, meta_item_dict): - xml_doc = minidom.Document() - item_key, item_value = meta_item_dict.items()[0] - item_node = self._meta_item_to_xml(xml_doc, item_key, item_value) - xml_doc.appendChild(item_node) - self._add_xmlns(item_node) - return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') - - def show(self, meta_item_dict): - return self._meta_item_to_xml_string(meta_item_dict['meta']) - - def update(self, meta_item_dict): - return self._meta_item_to_xml_string(meta_item_dict['meta']) - - def create_resource(): body_serializers = { 'application/xml': ImageMetadataXMLSerializer(), diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d0317583e..6c7c0feb9 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -278,9 +278,9 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): xmlns = wsgi.XMLNS_V11 def __init__(self): - self.metadata_serializer = image_metadata.ImageMetadataXMLSerializer() + self.metadata_serializer = common.MetadataXMLSerializer() - def _image_to_xml(self, xml_doc, image): + def image_to_xml(self, xml_doc, image): image_node = xml_doc.createElement('image') image_node.setAttribute('id', str(image['id'])) image_node.setAttribute('name', image['name']) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 93f8e832c..b07b903f8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,6 +17,7 @@ import base64 import traceback from webob import exc +from xml.dom import minidom from nova import compute from nova import db @@ -612,6 +613,94 @@ class HeadersSerializer(wsgi.ResponseHeadersSerializer): response.status_int = 204 +class ServerXMLSerializer(wsgi.XMLDictSerializer): + + xmlns = wsgi.XMLNS_V11 + + def __init__(self): + self.metadata_serializer = common.MetadataXMLSerializer() + + def _create_basic_entity_node(self, xml_doc, id, links, name): + basic_node = xml_doc.createElement(name) + basic_node.setAttribute('id', str(id)) + link_nodes = self._create_link_nodes(xml_doc, links) + for link_node in link_nodes: + basic_node.appendChild(link_node) + return basic_node + + def _create_metadata_node(self, xml_doc, metadata): + return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata) + + def _create_addresses_node(self, xml_doc, addresses): + addresses_node = xml_doc.createElement('addresses') + for name, network_dict in addresses.items(): + network_node = self._create_network_node(xml_doc, + name, + network_dict) + addresses_node.appendChild(network_node) + return addresses_node + + def _create_network_node(self, xml_doc, network_name, network_dict): + network_node = xml_doc.createElement('network') + network_node.setAttribute('id', network_name) + for ip in network_dict: + ip_node = xml_doc.createElement('ip') + ip_node.setAttribute('version', str(ip['version'])) + ip_node.setAttribute('addr', ip['addr']) + network_node.appendChild(ip_node) + return network_node + + + def _add_server_attributes(self, node, server): + node.setAttribute('id', str(server['id'])) + node.setAttribute('uuid', str(server['uuid'])) + node.setAttribute('hostId', str(server['hostId'])) + node.setAttribute('name', server['name']) + node.setAttribute('created', server['created']) + node.setAttribute('updated', server['updated']) + node.setAttribute('status', server['status']) + if 'progress' in server: + node.setAttribute('progress', str(server['progress'])) + + def _server_to_xml_detailed(self, xml_doc, server): + server_node = xml_doc.createElement('server') + self._add_server_attributes(server_node, server) + + link_nodes = self._create_link_nodes(xml_doc, + server['links']) + for link_node in link_nodes: + server_node.appendChild(link_node) + + image_node = self._create_basic_entity_node(xml_doc, + server['image']['id'], + server['image']['links'], + 'image') + server_node.appendChild(image_node) + + flavor_node = self._create_basic_entity_node(xml_doc, + server['flavor']['id'], + server['flavor']['links'], + 'flavor') + server_node.appendChild(flavor_node) + + metadata = server.get('metadata', {}).items() + if len(metadata) > 0: + metadata_node = self._create_metadata_node(xml_doc, metadata) + server_node.appendChild(metadata_node) + + addresses_node = self._create_addresses_node(xml_doc, + server['addresses']) + server_node.appendChild(addresses_node) + + return server_node + + def show(self, server_dict): + xml_doc = minidom.Document() + node = self._server_to_xml_detailed(xml_doc, + server_dict['server']) + return self.to_xml_string(node, True) + + def create_resource(version='1.0'): controller = { '1.0': ControllerV10, -- cgit From 7af043463a350cfc71c45ff719354511173b5c39 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sat, 16 Jul 2011 19:39:27 -0400 Subject: Moved Metadata Serialization Test --- nova/api/openstack/image_metadata.py | 3 ++- nova/api/openstack/images.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index dd1a3f130..d1065a4e3 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -21,6 +21,7 @@ from nova import flags from nova import image from nova import quota from nova import utils +from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -112,7 +113,7 @@ class Controller(object): def create_resource(): body_serializers = { - 'application/xml': ImageMetadataXMLSerializer(), + 'application/xml': common.MetadataXMLSerializer(), } serializer = wsgi.ResponseSerializer(body_serializers) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 6c7c0feb9..df7bf0cf9 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -280,7 +280,7 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): def __init__(self): self.metadata_serializer = common.MetadataXMLSerializer() - def image_to_xml(self, xml_doc, image): + def _image_to_xml(self, xml_doc, image): image_node = xml_doc.createElement('image') image_node.setAttribute('id', str(image['id'])) image_node.setAttribute('name', image['name']) -- cgit From c538d38d890e74382e928d225e8abdc57da9760e Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sat, 16 Jul 2011 19:45:28 -0400 Subject: pep8 --- nova/api/openstack/image_metadata.py | 2 +- nova/api/openstack/servers.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index d1065a4e3..b9b782f92 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -21,7 +21,7 @@ from nova import flags from nova import image from nova import quota from nova import utils -from nova.api.openstack import common +from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import wsgi diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index b07b903f8..1a85fda58 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -650,7 +650,6 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): network_node.appendChild(ip_node) return network_node - def _add_server_attributes(self, node, server): node.setAttribute('id', str(server['id'])) node.setAttribute('uuid', str(server['uuid'])) -- cgit From 712493f65415a7a5fc727f6b316c66ef90f1cad5 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sun, 17 Jul 2011 14:50:44 -0400 Subject: added index to servers xml serializer --- nova/api/openstack/servers.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1a85fda58..8d59c8626 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -661,6 +661,16 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): if 'progress' in server: node.setAttribute('progress', str(server['progress'])) + def _server_to_xml(self, xml_doc, server): + server_node = xml_doc.createElement('server') + server_node.setAttribute('id', str(server['id'])) + server_node.setAttribute('name', server['name']) + link_nodes = self._create_link_nodes(xml_doc, + server['links']) + for link_node in link_nodes: + server_node.appendChild(link_node) + return server_node + def _server_to_xml_detailed(self, xml_doc, server): server_node = xml_doc.createElement('server') self._add_server_attributes(server_node, server) @@ -693,6 +703,25 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): return server_node + def _server_list_to_xml(self, xml_doc, servers, detailed): + container_node = xml_doc.createElement('servers') + if detailed: + server_to_xml = self._server_to_xml_detailed + else: + server_to_xml = self._server_to_xml + + for server in servers: + item_node = server_to_xml(xml_doc, server) + container_node.appendChild(item_node) + return container_node + + def index(self, servers_dict): + xml_doc = minidom.Document() + node = self._server_list_to_xml(xml_doc, + servers_dict['servers'], + detailed=False) + return self.to_xml_string(node, True) + def show(self, server_dict): xml_doc = minidom.Document() node = self._server_to_xml_detailed(xml_doc, -- cgit From b0a02feb7bd25380a75b83344e0ac3210a168387 Mon Sep 17 00:00:00 2001 From: John Tran Date: Sun, 17 Jul 2011 22:07:07 +0000 Subject: changed to avoid localization test failure --- nova/api/ec2/cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 5c0c79429..4923a7666 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -626,8 +626,8 @@ class CloudController(object): for values_for_rule in rulesvalues: values_for_rule['parent_group_id'] = security_group.id if self._security_group_rule_exists(security_group, values_for_rule): - raise exception.ApiError(_('%s - This rule already exists in group %s') - % (values_for_rule, group_name)) + raise exception.ApiError(_('%s - This rule already exists in group') + % values_for_rule) postvalues.append(values_for_rule) for values_for_rule in postvalues: -- cgit From baaaa80d36570d5734ac823bc49be8ff2477e5c2 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sun, 17 Jul 2011 21:24:02 -0400 Subject: added 'detail' to server XML serializer --- nova/api/openstack/servers.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8d59c8626..82630397f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -722,6 +722,13 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): detailed=False) return self.to_xml_string(node, True) + def detail(self, servers_dict): + xml_doc = minidom.Document() + node = self._server_list_to_xml(xml_doc, + servers_dict['servers'], + detailed=True) + return self.to_xml_string(node, True) + def show(self, server_dict): xml_doc = minidom.Document() node = self._server_to_xml_detailed(xml_doc, -- cgit From 8ab775585fee4af7b30a28a5bffae46c23ec76d1 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sun, 17 Jul 2011 21:36:57 -0400 Subject: added 'create' to server XML serializer --- nova/api/openstack/servers.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 82630397f..4a29e1454 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -735,6 +735,13 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): server_dict['server']) return self.to_xml_string(node, True) + def create(self, server_dict): + xml_doc = minidom.Document() + node = self._server_to_xml_detailed(xml_doc, + server_dict['server']) + node.setAttribute('adminPass', server_dict['server']['adminPass']) + return self.to_xml_string(node, True) + def create_resource(version='1.0'): controller = { -- cgit From e23e70afd096ca1d7ad22c776f6f439986bbc8b5 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sun, 17 Jul 2011 22:28:16 -0400 Subject: updated servers to use ServerXMLSerializer --- nova/api/openstack/servers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4a29e1454..a6229125d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -773,8 +773,7 @@ def create_resource(version='1.0'): headers_serializer = HeadersSerializer() body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=xmlns), + 'application/xml': ServerXMLSerializer(), } body_deserializers = { -- cgit From b9d316452a1e2a204e56d1434feade1ab0bd281c Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sun, 17 Jul 2011 22:50:10 -0400 Subject: Updated servers to choose XML serializer based on api version --- nova/api/openstack/servers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a6229125d..6d55b856d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -772,8 +772,13 @@ def create_resource(version='1.0'): headers_serializer = HeadersSerializer() + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), + '1.1': ServerXMLSerializer(), + }[version] + body_serializers = { - 'application/xml': ServerXMLSerializer(), + 'application/xml': xml_serializer, } body_deserializers = { -- cgit From ab42384131077bae3986141279b605d8f994143c Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 18 Jul 2011 03:45:16 +0000 Subject: resolved pep8 issues --- nova/api/ec2/cloud.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4923a7666..113faf13b 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -443,16 +443,16 @@ class CloudController(object): def _rule_args_to_dict(self, context, kwargs): rules = [] - if not kwargs.has_key('groups') and not kwargs.has_key('ip_ranges'): + if not 'groups' in kwargs and not 'ip_ranges' in kwargs: rule = self._rule_dict_last_step(context, **kwargs) if rule: rules.append(rule) return rules - if kwargs.has_key('ip_ranges'): + if 'ip_ranges' in kwargs: rules = self._cidr_args_split(kwargs) finalset = [] for rule in rules: - if rule.has_key('groups'): + if 'groups' in rule: groups_values = self._groups_args_split(rule) for groups_value in groups_values: finalset.append(groups_value) @@ -462,7 +462,7 @@ class CloudController(object): return finalset def _cidr_args_split(self, kwargs): - cidr_args_split= [] + cidr_args_split = [] cidrs = kwargs['ip_ranges'] for key, cidr in cidrs.iteritems(): mykwargs = kwargs.copy() @@ -472,16 +472,16 @@ class CloudController(object): return cidr_args_split def _groups_args_split(self, kwargs): - groups_args_split= [] + groups_args_split = [] groups = kwargs['groups'] for key, group in groups.iteritems(): mykwargs = kwargs.copy() del mykwargs['groups'] - if group.has_key('group_name'): + if 'group_name' in group: mykwargs['source_security_group_name'] = group['group_name'] - if group.has_key('user_id'): + if 'user_id' in group: mykwargs['source_security_group_owner_id'] = group['user_id'] - if group.has_key('group_id'): + if 'group_id' in group: mykwargs['source_security_group_id'] = group['group_id'] groups_args_split.append(mykwargs) return groups_args_split @@ -621,22 +621,25 @@ class CloudController(object): for values in prevalues: rulesvalues = self._rule_args_to_dict(context, values) if not rulesvalues: - raise exception.ApiError(_("%s Not enough parameters to build a " - "valid rule." % rulesvalues)) + err = "%s Not enough parameters to build a valid rule" + raise exception.ApiError(_(err % rulesvalues)) for values_for_rule in rulesvalues: values_for_rule['parent_group_id'] = security_group.id - if self._security_group_rule_exists(security_group, values_for_rule): - raise exception.ApiError(_('%s - This rule already exists in group') - % values_for_rule) + if self._security_group_rule_exists(security_group, + values_for_rule): + err = '%s - This rule already exists in group' + raise exception.ApiError(_(err) % values_for_rule) postvalues.append(values_for_rule) for values_for_rule in postvalues: - security_group_rule = db.security_group_rule_create(context, values_for_rule) + security_group_rule = db.security_group_rule_create(context, + values_for_rule) self.compute_api.trigger_security_group_rules_refresh(context, security_group_id=security_group['id']) - group = db.security_group_get_by_name(context, context.project_id, security_group['name']) + group = db.security_group_get_by_name(context, context.project_id, + security_group['name']) return True def _get_source_project_id(self, context, source_security_group_owner_id): -- cgit From 55de6a262f40024c2c9c8f7c6e84c56eaa14d206 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 18 Jul 2011 17:34:36 -0400 Subject: adding fixed_ipv6 property to VirtualInterface model; exposing ipv6 in api --- nova/api/openstack/views/addresses.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index a242efa45..77310e4b1 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -50,22 +50,36 @@ class ViewBuilderV11(ViewBuilder): if network_label not in networks: networks[network_label] = [] - networks[network_label].extend(self._extract_ipv4(interface)) + ip_addresses = list(self._extract_ipv4_addresses(interface)) + + ipv6_address = self._extract_ipv6_address(interface) + if ipv6_address is not None: + ip_addresses.append(ipv6_address) + + networks[network_label].extend(ip_addresses) return networks def build_network(self, interfaces, network_label): for interface in interfaces: if interface['network']['label'] == network_label: - ips = self._extract_ipv4(interface) - return {network_label: list(ips)} + ips = list(self._extract_ipv4_addresses(interface)) + ipv6 = self._extract_ipv6_address(interface) + if ipv6 is not None: + ips.append(ipv6) + return {network_label: ips} return None - def _extract_ipv4(self, interface): + def _extract_ipv4_addresses(self, interface): for fixed_ip in interface['fixed_ips']: yield self._build_ip_entity(fixed_ip['address'], 4) for floating_ip in fixed_ip.get('floating_ips', []): yield self._build_ip_entity(floating_ip['address'], 4) + def _extract_ipv6_address(self, interface): + fixed_ipv6 = interface.get('fixed_ipv6') + if fixed_ipv6 is not None: + return self._build_ip_entity(fixed_ipv6, 6) + def _build_ip_entity(self, address, version): return {'addr': address, 'version': version} -- cgit From 82e2eeb5a097f1c3c6cb56fc3dfa862575f5da9a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Jul 2011 09:36:37 -0400 Subject: respecting use_ipv6 flag if set to False --- nova/api/openstack/views/addresses.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 77310e4b1..ddbf7a144 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -15,9 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. +from nova import flags from nova import utils from nova.api.openstack import common +FLAGS = flags.FLAGS + class ViewBuilder(object): """Models a server addresses response as a python dictionary.""" @@ -52,9 +55,10 @@ class ViewBuilderV11(ViewBuilder): ip_addresses = list(self._extract_ipv4_addresses(interface)) - ipv6_address = self._extract_ipv6_address(interface) - if ipv6_address is not None: - ip_addresses.append(ipv6_address) + if FLAGS.use_ipv6: + ipv6_address = self._extract_ipv6_address(interface) + if ipv6_address is not None: + ip_addresses.append(ipv6_address) networks[network_label].extend(ip_addresses) -- cgit From 4b4bebad3b44e7b55e55a005a3629aebf50ecfa2 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 19 Jul 2011 15:51:45 -0400 Subject: Updates to the compute API and manager so that rebuild, reboot, snapshots, and password resets work with the most recent versions of novaclient. --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 93f8e832c..e16221fd8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -454,7 +454,7 @@ class ControllerV10(Controller): def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] - instance_id = int(instance_id) + instance_id = instance_id try: image_id = info["rebuild"]["imageId"] @@ -569,7 +569,7 @@ class ControllerV11(Controller): def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] - instance_id = int(instance_id) + instance_id = instance_id try: image_href = info["rebuild"]["imageRef"] -- cgit From bc2747faf2164833b3e5412dd226f9ee431b1bbf Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 19 Jul 2011 17:51:43 -0400 Subject: updated links to use proper atom:link per spec --- nova/api/openstack/versions.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index a634c3267..450b42765 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -17,6 +17,7 @@ import webob import webob.dec +from xml.dom import minidom import nova.api.openstack.views.versions from nova.api.openstack import wsgi @@ -32,7 +33,7 @@ class Versions(wsgi.Resource): } body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), + 'application/xml': VersionsXMLSerializer(metadata=metadata), } serializer = wsgi.ResponseSerializer(body_serializers) @@ -54,3 +55,36 @@ class Versions(wsgi.Resource): builder = nova.api.openstack.views.versions.get_view_builder(request) versions = [builder.build(version) for version in version_objs] return dict(versions=versions) + +class VersionsXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self, metadata=None, xmlns=None): + super(VersionsXMLSerializer, self).__init__(metadata, xmlns) + + def _versions_to_xml(self, versions): + root = self.xml_doc.createElement('versions') + + for version in versions: + root.appendChild(self._create_version_node(version)) + + return root + + def _create_version_node(self, version): + version_node = self.xml_doc.createElement('version') + version_node.setAttribute('id', version['id']) + version_node.setAttribute('status', version['status']) + #TODO(wwolf) need 'updated' attribute too + + for link in version['links']: + link_node = self.xml_doc.createElement('atom:link') + link_node.setAttribute('rel', link['rel']) + link_node.setAttribute('href', link['href']) + version_node.appendChild(link_node) + + return version_node + + + def default(self, data): + self.xml_doc = minidom.Document() + node = self._versions_to_xml(data['versions']) + + return self.to_xml_string(node) -- cgit From 27f326c712e485322003ccdc13acfd04a6fdb119 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 09:59:56 -0400 Subject: added 'update' field to versions --- nova/api/openstack/versions.py | 6 +++++- nova/api/openstack/views/versions.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 450b42765..1b1fc41ab 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -45,10 +45,14 @@ class Versions(wsgi.Resource): { "id": "v1.1", "status": "CURRENT", + #TODO(wwolf) get correct value for these + "updated": "2011-7-18T11:30:00Z", }, { "id": "v1.0", "status": "DEPRECATED", + #TODO(wwolf) get correct value for these + "updated": "2010-10-09T11:30:00Z", }, ] @@ -72,7 +76,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): version_node = self.xml_doc.createElement('version') version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) - #TODO(wwolf) need 'updated' attribute too + version_node.setAttribute('updated', version['updated']) for link in version['links']: link_node = self.xml_doc.createElement('atom:link') diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index d0145c94a..e815974f1 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -36,6 +36,7 @@ class ViewBuilder(object): version = { "id": version_data["id"], "status": version_data["status"], + "updated": version_data["updated"], "links": self._build_links(version_data), } -- cgit From 1ba04869623a0152a487a50e25bfce0ee6a65f53 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 20 Jul 2011 10:30:03 -0400 Subject: Ya! Apparently sleep helps me fix failing tests. --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e16221fd8..1e09b6ea0 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -466,7 +466,7 @@ class ControllerV10(Controller): try: self.compute_api.rebuild(context, instance_id, image_id) except exception.BuildInProgress: - msg = _("Instance %d is currently being rebuilt.") % instance_id + msg = _("Instance %s is currently being rebuilt.") % instance_id LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) @@ -590,7 +590,7 @@ class ControllerV11(Controller): self.compute_api.rebuild(context, instance_id, image_href, name, metadata, personalities) except exception.BuildInProgress: - msg = _("Instance %d is currently being rebuilt.") % instance_id + msg = _("Instance %s is currently being rebuilt.") % instance_id LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) -- cgit From a35a70fbaef0ef6634213308d5a68ee60bd714f2 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 13:12:18 -0400 Subject: initial changes for application/atom+xml for versions --- nova/api/openstack/versions.py | 98 ++++++++++++++++++++++++++++++++++++++---- nova/api/openstack/wsgi.py | 19 +++++--- 2 files changed, 103 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 1b1fc41ab..a83472e15 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -23,7 +23,11 @@ import nova.api.openstack.views.versions from nova.api.openstack import wsgi +ATOM_XMLNS = "http://www.w3.org/2005/Atom" + + class Versions(wsgi.Resource): + def __init__(self): metadata = { "attributes": { @@ -33,11 +37,19 @@ class Versions(wsgi.Resource): } body_serializers = { + 'application/atom+xml': VersionsAtomSerializer(metadata=metadata), 'application/xml': VersionsXMLSerializer(metadata=metadata), } serializer = wsgi.ResponseSerializer(body_serializers) - wsgi.Resource.__init__(self, None, serializer=serializer) + supported_content_types = ('application/json', + 'application/xml', + 'application/atom+xml') + deserializer = wsgi.RequestDeserializer( + supported_content_types=supported_content_types) + + wsgi.Resource.__init__(self, None, serializer=serializer, + deserializer=deserializer) def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" @@ -61,11 +73,8 @@ class Versions(wsgi.Resource): return dict(versions=versions) class VersionsXMLSerializer(wsgi.XMLDictSerializer): - def __init__(self, metadata=None, xmlns=None): - super(VersionsXMLSerializer, self).__init__(metadata, xmlns) - def _versions_to_xml(self, versions): - root = self.xml_doc.createElement('versions') + root = self._xml_doc.createElement('versions') for version in versions: root.appendChild(self._create_version_node(version)) @@ -73,13 +82,13 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return root def _create_version_node(self, version): - version_node = self.xml_doc.createElement('version') + version_node = self._xml_doc.createElement('version') version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) version_node.setAttribute('updated', version['updated']) for link in version['links']: - link_node = self.xml_doc.createElement('atom:link') + link_node = self._xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) version_node.appendChild(link_node) @@ -88,7 +97,80 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def default(self, data): - self.xml_doc = minidom.Document() + self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) return self.to_xml_string(node) + +class VersionsAtomSerializer(wsgi.XMLDictSerializer): + def __init__(self, metadata=None, xmlns=None): + if not xmlns: + self.xmlns = ATOM_XMLNS + else: + self.xmlns = xmlns + + def _create_text_elem(self, name, text, type=None): + elem = self._xml_doc.createElement(name) + if type: + elem.setAttribute('type', type) + elem_text = self._xml_doc.createTextNode(text) + elem.appendChild(elem_text) + return elem + + def _create_meta(self, root): + title = self._create_text_elem('title', 'Available API Versions', + type='text') + #TODO(wwolf): what should updated be? + updated = self._create_text_elem('updated', '2010-12-12T18:30:02.25Z') + #TODO(wwolf): get URI + id = self._create_text_elem('id', '') + #TODO(wwolf): get link info + link = self._xml_doc.createElement('link') + link.setAttribute('rel', 'rel') + link.setAttribute('href', 'href') + + author = self._xml_doc.createElement('author') + author_name = self._create_text_elem('name', 'Rackspace') + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com') + author.appendChild(author_name) + author.appendChild(author_uri) + + root.appendChild(title) + root.appendChild(updated) + root.appendChild(id) + root.appendChild(link) + root.appendChild(author) + + def _create_version_entries(self, root, versions): + for version in versions: + entry = self._xml_doc.createElement('entry') + #TODO(wwolf) GET URI + id = self._create_text_elem('id', 'URI') + title = self._create_text_elem('title', + 'Version %s' % version['id'], + type='text') + updated = self._create_text_elem('updated', version['updated']) + #TODO(wwolf): get link info + link = self._xml_doc.createElement('link') + link.setAttribute('rel', 'rel') + link.setAttribute('href', 'href') + content = self._create_text_elem('content', + 'Version %s %s (%s)' % + (version['id'], + version['status'], + version['updated'])) + + entry.appendChild(id) + entry.appendChild(title) + entry.appendChild(updated) + entry.appendChild(link) + entry.appendChild(content) + root.appendChild(entry) + + def default(self, data): + self._xml_doc = minidom.Document() + node = self._xml_doc.createElementNS(self.xmlns, 'feed') + self._create_meta(node) + self._create_version_entries(node, data['versions']) + + return self.to_xml_string(node) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c3f841aa5..9df6fd058 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -20,21 +20,22 @@ LOG = logging.getLogger('nova.api.openstack.wsgi') class Request(webob.Request): """Add some Openstack API-specific logic to the base webob.Request.""" - def best_match_content_type(self): + def best_match_content_type(self, supported_content_types=None): """Determine the requested response content-type. Based on the query extension then the Accept header. """ - supported = ('application/json', 'application/xml') + supported_content_types = supported_content_types or \ + ('application/json', 'application/xml') parts = self.path.rsplit('.', 1) if len(parts) > 1: ctype = 'application/{0}'.format(parts[1]) - if ctype in supported: + if ctype in supported_content_types: return ctype - bm = self.accept.best_match(supported) + bm = self.accept.best_match(supported_content_types) # default to application/json if we don't find a preference return bm or 'application/json' @@ -151,7 +152,12 @@ class RequestHeadersDeserializer(ActionDispatcher): class RequestDeserializer(object): """Break up a Request object into more useful pieces.""" - def __init__(self, body_deserializers=None, headers_deserializer=None): + def __init__(self, body_deserializers=None, headers_deserializer=None, + supported_content_types=None): + + self.supported_content_types = supported_content_types or \ + ('application/json', 'application/xml') + self.body_deserializers = { 'application/xml': XMLDeserializer(), 'application/json': JSONDeserializer(), @@ -213,7 +219,7 @@ class RequestDeserializer(object): raise exception.InvalidContentType(content_type=content_type) def get_expected_content_type(self, request): - return request.best_match_content_type() + return request.best_match_content_type(self.supported_content_types) def get_action_args(self, request_environment): """Parse dictionary created by routes library.""" @@ -412,6 +418,7 @@ class Resource(wsgi.Application): serialized by requested content type. """ + def __init__(self, controller, deserializer=None, serializer=None): """ :param controller: object that implement methods created by routes lib -- cgit From 1b423b464e0dd849d1a6dafde08b3c4420217151 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 20 Jul 2011 13:52:22 -0400 Subject: Set the status_int on fault wrapped exceptions. Fixes WSGI logging issues when faults are returned. Updated so that webob exceptions aren't used for the happy path (HTTP 200 responses). We now return a proper webob object response in these cases. This fixes issues where HTML/XML would get returned with the old style happy path exceptions. --- nova/api/openstack/consoles.py | 3 ++- nova/api/openstack/contrib/multinic.py | 5 ++-- nova/api/openstack/contrib/volumes.py | 5 ++-- nova/api/openstack/faults.py | 1 + nova/api/openstack/servers.py | 49 ++++++++++++++++------------------ 5 files changed, 32 insertions(+), 31 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 7a43fba96..9c7b37f0d 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -16,6 +16,7 @@ # under the License. from webob import exc +import webob from nova import console from nova import exception @@ -86,7 +87,7 @@ class Controller(object): int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def create_resource(): diff --git a/nova/api/openstack/contrib/multinic.py b/nova/api/openstack/contrib/multinic.py index 841061721..da8dcee5d 100644 --- a/nova/api/openstack/contrib/multinic.py +++ b/nova/api/openstack/contrib/multinic.py @@ -16,6 +16,7 @@ """The multinic extension.""" from webob import exc +import webob from nova import compute from nova import log as logging @@ -103,7 +104,7 @@ class Multinic(extensions.ExtensionDescriptor): except Exception, e: LOG.exception(_("Error in addFixedIp %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _remove_fixed_ip(self, input_dict, req, id): """Removes an IP from an instance.""" @@ -122,4 +123,4 @@ class Multinic(extensions.ExtensionDescriptor): except Exception, e: LOG.exception(_("Error in removeFixedIp %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index e5e2c5b50..827e36097 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -16,6 +16,7 @@ """The volumes extension.""" from webob import exc +import webob from nova import compute from nova import exception @@ -104,7 +105,7 @@ class VolumeController(object): self.volume_api.delete(context, volume_id=id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def index(self, req): """Returns a summary list of volumes.""" @@ -279,7 +280,7 @@ class VolumeAttachmentController(object): self.compute_api.detach_volume(context, volume_id=volume_id) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _items(self, req, server_id, entity_maker): """Returns a list of attachments, transformed through entity_maker.""" diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index b9a23c126..24cde69e4 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -40,6 +40,7 @@ class Fault(webob.exc.HTTPException): def __init__(self, exception): """Create a Fault for the given webob.exc.exception.""" self.wrapped_exc = exception + self.status_int = exception.status_int @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 93f8e832c..3b3d0685d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,6 +17,7 @@ import base64 import traceback from webob import exc +import webob from nova import compute from nova import db @@ -189,7 +190,7 @@ class Controller(object): except Exception, e: LOG.exception(_("Error in revert-resize %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_resize(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -207,7 +208,7 @@ class Controller(object): except Exception, e: LOG.exception(_("Error in reboot %s"), e) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_migrate(self, input_dict, req, id): try: @@ -215,7 +216,7 @@ class Controller(object): except Exception, e: LOG.exception(_("Error in migrate %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def lock(self, req, id): @@ -231,7 +232,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::lock %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def unlock(self, req, id): @@ -247,7 +248,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::unlock %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def get_lock(self, req, id): @@ -262,7 +263,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::get_lock %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def reset_network(self, req, id, body): @@ -277,7 +278,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::reset_network %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def inject_network_info(self, req, id, body): @@ -292,7 +293,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::inject_network_info %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def pause(self, req, id, body): @@ -304,7 +305,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::pause %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def unpause(self, req, id, body): @@ -316,7 +317,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::unpause %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def suspend(self, req, id, body): @@ -328,7 +329,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::suspend %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def resume(self, req, id, body): @@ -340,7 +341,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::resume %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def rescue(self, req, id): @@ -352,7 +353,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::rescue %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def unrescue(self, req, id): @@ -364,7 +365,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::unrescue %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def get_ajax_console(self, req, id): @@ -374,7 +375,7 @@ class Controller(object): int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def get_vnc_console(self, req, id): @@ -384,7 +385,7 @@ class Controller(object): int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def diagnostics(self, req, id): @@ -416,7 +417,7 @@ class ControllerV10(Controller): self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _image_ref_from_req_data(self, data): return data['server']['imageId'] @@ -450,7 +451,7 @@ class ControllerV10(Controller): except Exception, e: LOG.exception(_("Error in resize %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] @@ -470,9 +471,7 @@ class ControllerV10(Controller): LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - response = exc.HTTPAccepted() - response.empty_body = True - return response + return webob.Response(status_int=202) def _get_server_admin_password(self, server): """ Determine the admin password for a server on creation """ @@ -519,7 +518,7 @@ class ControllerV11(Controller): msg = _("Invalid adminPass") return exc.HTTPBadRequest(explanation=msg) self.compute_api.set_admin_password(context, id, password) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _limit_items(self, items, req): return common.limited_by_marker(items, req) @@ -565,7 +564,7 @@ class ControllerV11(Controller): except Exception, e: LOG.exception(_("Error in resize %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] @@ -594,9 +593,7 @@ class ControllerV11(Controller): LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - response = exc.HTTPAccepted() - response.empty_body = True - return response + return webob.Response(status_int=202) def get_default_xmlns(self, req): return common.XML_NS_V11 -- cgit From 148b4095a5f18f192ae243b02b4070af384c1152 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 16:47:17 -0400 Subject: added versions list atom test and it passes --- nova/api/openstack/versions.py | 68 ++++++++++++++++++++++++------------ nova/api/openstack/views/versions.py | 2 +- 2 files changed, 46 insertions(+), 24 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index a83472e15..49633df88 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +from datetime import datetime import webob import webob.dec from xml.dom import minidom @@ -58,7 +59,7 @@ class Versions(wsgi.Resource): "id": "v1.1", "status": "CURRENT", #TODO(wwolf) get correct value for these - "updated": "2011-7-18T11:30:00Z", + "updated": "2011-07-18T11:30:00Z", }, { "id": "v1.0", @@ -117,60 +118,81 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): elem.appendChild(elem_text) return elem - def _create_meta(self, root): + def _get_most_recent_update(self, versions): + recent = None + for version in versions: + updated = datetime.strptime(version['updated'],'%Y-%m-%dT%H:%M:%SZ') + if not recent: + recent = updated + elif updated > recent: + recent = updated + + return recent.strftime('%Y-%m-%dT%H:%M:%SZ') + + def _get_base_url(self, link_href): + # Make sure no trailing / + link_href = link_href.rstrip('/') + return link_href.rsplit('/',1)[0] + '/' + + def _create_meta(self, root, versions): title = self._create_text_elem('title', 'Available API Versions', type='text') - #TODO(wwolf): what should updated be? - updated = self._create_text_elem('updated', '2010-12-12T18:30:02.25Z') - #TODO(wwolf): get URI - id = self._create_text_elem('id', '') - #TODO(wwolf): get link info + # Set this updated to the most recently updated version + recent = self._get_most_recent_update(versions) + updated = self._create_text_elem('updated', recent) + + base_url = self._get_base_url(versions[0]['links'][0]['href']) + id = self._create_text_elem('id', base_url) link = self._xml_doc.createElement('link') - link.setAttribute('rel', 'rel') - link.setAttribute('href', 'href') + link.setAttribute('rel', 'self') + link.setAttribute('href', base_url) author = self._xml_doc.createElement('author') author_name = self._create_text_elem('name', 'Rackspace') - author_uri = self._create_text_elem('uri', 'http://www.rackspace.com') + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/') author.appendChild(author_name) author.appendChild(author_uri) root.appendChild(title) root.appendChild(updated) root.appendChild(id) - root.appendChild(link) root.appendChild(author) + root.appendChild(link) def _create_version_entries(self, root, versions): for version in versions: entry = self._xml_doc.createElement('entry') - #TODO(wwolf) GET URI - id = self._create_text_elem('id', 'URI') + + id = self._create_text_elem('id', version['links'][0]['href']) title = self._create_text_elem('title', 'Version %s' % version['id'], type='text') updated = self._create_text_elem('updated', version['updated']) - #TODO(wwolf): get link info - link = self._xml_doc.createElement('link') - link.setAttribute('rel', 'rel') - link.setAttribute('href', 'href') + + entry.appendChild(id) + entry.appendChild(title) + entry.appendChild(updated) + + for link in version['links']: + link_node = self._xml_doc.createElement('link') + link_node.setAttribute('rel', link['rel']) + link_node.setAttribute('href', link['href']) + entry.appendChild(link_node) + content = self._create_text_elem('content', 'Version %s %s (%s)' % (version['id'], version['status'], - version['updated'])) + version['updated']), + type='text') - entry.appendChild(id) - entry.appendChild(title) - entry.appendChild(updated) - entry.appendChild(link) entry.appendChild(content) root.appendChild(entry) def default(self, data): self._xml_doc = minidom.Document() node = self._xml_doc.createElementNS(self.xmlns, 'feed') - self._create_meta(node) + self._create_meta(node, data['versions']) self._create_version_entries(node, data['versions']) return self.to_xml_string(node) diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index e815974f1..9fa8f49dc 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -57,4 +57,4 @@ class ViewBuilder(object): def generate_href(self, version_number): """Create an url that refers to a specific version_number.""" - return os.path.join(self.base_url, version_number) + return os.path.join(self.base_url, version_number) + '/' -- cgit From 38fb47ab2a12be0b169e3f51f4da413efc041f9c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 20 Jul 2011 17:06:36 -0400 Subject: adding flavors xml serialization --- nova/api/openstack/flavors.py | 55 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 6fab13147..b4bda68d4 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -16,6 +16,7 @@ # under the License. import webob +import xml.dom.minidom as minidom from nova import db from nova import exception @@ -74,19 +75,65 @@ class ControllerV11(Controller): return views.flavors.ViewBuilderV11(base_url) +class FlavorXMLSerializer(wsgi.XMLDictSerializer): + + def __init__(self): + super(FlavorXMLSerializer, self).__init__(xmlns=wsgi.XMLNS_V11) + + def _flavor_to_xml(self, xml_doc, flavor, detailed): + flavor_node = xml_doc.createElement('flavor') + flavor_node.setAttribute('id', str(flavor['id'])) + flavor_node.setAttribute('name', flavor['name']) + + if detailed: + flavor_node.setAttribute('ram', str(flavor['ram'])) + flavor_node.setAttribute('disk', str(flavor['disk'])) + + link_nodes = self._create_link_nodes(xml_doc, flavor['links']) + for link_node in link_nodes: + flavor_node.appendChild(link_node) + return flavor_node + + def _flavors_list_to_xml(self, xml_doc, flavors, detailed): + container_node = xml_doc.createElement('flavors') + + for flavor in flavors: + item_node = self._flavor_to_xml(xml_doc, flavor, detailed) + container_node.appendChild(item_node) + return container_node + + def show(self, flavor_container): + xml_doc = minidom.Document() + flavor = flavor_container['flavor'] + node = self._flavor_to_xml(xml_doc, flavor, True) + return self.to_xml_string(node, True) + + def detail(self, flavors_container): + xml_doc = minidom.Document() + flavors = flavors_container['flavors'] + node = self._flavors_list_to_xml(xml_doc, flavors, True) + return self.to_xml_string(node, True) + + def index(self, flavors_container): + xml_doc = minidom.Document() + flavors = flavors_container['flavors'] + node = self._flavors_list_to_xml(xml_doc, flavors, False) + return self.to_xml_string(node, True) + + def create_resource(version='1.0'): controller = { '1.0': ControllerV10, '1.1': ControllerV11, }[version]() - xmlns = { - '1.0': wsgi.XMLNS_V10, - '1.1': wsgi.XMLNS_V11, + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10), + '1.1': FlavorXMLSerializer(), }[version] body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns), + 'application/xml': xml_serializer, } serializer = wsgi.ResponseSerializer(body_serializers) -- cgit From 97503418bea47ca38d2181e5dc5fa710d6e15df3 Mon Sep 17 00:00:00 2001 From: Yoshiaki Tamura Date: Thu, 21 Jul 2011 21:44:12 +0900 Subject: Add OpenStack API support for block_device_mapping. This patch enables boot from volume feature already implemented in EC2 API, as an OpenStack API extension. --- nova/api/openstack/contrib/volumes.py | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index e5e2c5b50..43e6a8cd9 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -21,10 +21,12 @@ from nova import compute from nova import exception from nova import flags from nova import log as logging +from nova import quota from nova import volume from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import faults +from nova.api.openstack import servers LOG = logging.getLogger("nova.api.volumes") @@ -296,6 +298,53 @@ class VolumeAttachmentController(object): return {'volumeAttachments': res} +class BootFromVolumeController(servers.ControllerV11): + """The boot from volume API controller for the Openstack API.""" + + def _create_instance(self, context, instance_type, image_href, **kwargs): + try: + return self.compute_api.create(context, instance_type, + image_href, **kwargs) + except quota.QuotaError as error: + self.helper._handle_quota_error(error) + except exception.ImageNotFound as error: + msg = _("Can not find requested image") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + + def create(self, req, body): + """ Creates a new server for a given user """ + extra_values = None + try: + + def get_kwargs(context, instance_type, image_href, **kwargs): + kwargs['context'] = context + kwargs['instance_type'] = instance_type + kwargs['image_href'] = image_href + return kwargs + + extra_values, kwargs = self.helper.create_instance(req, body, + get_kwargs) + + block_device_mapping = body['server'].get('block_device_mapping') + kwargs['block_device_mapping'] = block_device_mapping + + instances = self._create_instance(**kwargs) + except faults.Fault, f: + return f + + # We can only return 1 instance via the API, if we happen to + # build more than one... instances is a list, so we'll just + # use the first one.. + inst = instances[0] + for key in ['instance_type', 'image_ref']: + inst[key] = extra_values[key] + + builder = self._get_view_builder(req) + server = builder.build(inst, is_detail=True) + server['server']['adminPass'] = extra_values['password'] + return server + + class Volumes(extensions.ExtensionDescriptor): def get_name(self): return "Volumes" @@ -329,4 +378,8 @@ class Volumes(extensions.ExtensionDescriptor): collection_name='servers')) resources.append(res) + res = extensions.ResourceExtension('os-volumes_boot', + BootFromVolumeController()) + resources.append(res) + return resources -- cgit From b2f7ae2ad7762531a9200799de4be9849cd3b42c Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 09:43:25 -0400 Subject: Added LimitsXMLSerializer Added LimitsViewBuidlerV11Test test case --- nova/api/openstack/limits.py | 61 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index bc76547d8..86afa3b62 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -25,6 +25,7 @@ import re import time import urllib import webob.exc +from xml.dom import minidom from collections import defaultdict @@ -76,6 +77,58 @@ class LimitsControllerV11(LimitsController): return limits_views.ViewBuilderV11() +class LimitsXMLSerializer(wsgi.XMLDictSerializer): + + xmlns = wsgi.XMLNS_V11 + + def __init__(self): + pass + + def _create_rates_node(self, xml_doc, rates): + rates_node = xml_doc.createElement('rates') + for rate in rates: + rate_node = xml_doc.createElement('rate') + rate_node.setAttribute('uri', rate['uri']) + rate_node.setAttribute('regex', rate['regex']) + + for limit in rate['limit']: + limit_node = xml_doc.createElement('limit') + limit_node.setAttribute('value', str(limit['value'])) + limit_node.setAttribute('verb', limit['verb']) + limit_node.setAttribute('remaining', str(limit['remaining'])) + limit_node.setAttribute('unit', limit['unit']) + limit_node.setAttribute('next-available', + str(limit['next-available'])) + rate_node.appendChild(limit_node) + + rates_node.appendChild(rate_node) + return rates_node + + def _create_absolute_node(self, xml_doc, absolutes): + absolute_node = xml_doc.createElement('absolute') + for key, value in absolutes.iteritems(): + limit_node = xml_doc.createElement('limit') + limit_node.setAttribute('name', key) + limit_node.setAttribute('value', str(value)) + absolute_node.appendChild(limit_node) + return absolute_node + + def _limits_to_xml(self, xml_doc, limits): + limits_node = xml_doc.createElement('limits') + rates_node = self._create_rates_node(xml_doc, limits['rate']) + limits_node.appendChild(rates_node) + + absolute_node = self._create_absolute_node(xml_doc, limits['absolute']) + limits_node.appendChild(absolute_node) + + return limits_node + + def index(self, limits_dict): + xml_doc = minidom.Document() + node = self._limits_to_xml(xml_doc, limits_dict['limits']) + return self.to_xml_string(node, False) + + def create_resource(version='1.0'): controller = { '1.0': LimitsControllerV10, @@ -97,9 +150,13 @@ def create_resource(version='1.0'): }, } + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(xmlns=xmlns, metadata=metadata), + '1.1': LimitsXMLSerializer(), + }[version] + body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, - metadata=metadata), + 'application/xml': xml_serializer, } serializer = wsgi.ResponseSerializer(body_serializers) -- cgit From 126714f297fc29c0a0c930e7e1f351bf114b3795 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 10:02:16 -0400 Subject: Perform fault wrapping in the openstack WSGI controller. This allows us to just raise webob Exceptions in OS API controllers with the appropriate explanations set. This resolves some inconsistencies with exception raising and returning that could cause HTML output to occur when an exception was raised. --- nova/api/openstack/accounts.py | 6 +- nova/api/openstack/backup_schedules.py | 9 ++- nova/api/openstack/common.py | 19 +++--- nova/api/openstack/consoles.py | 7 +-- nova/api/openstack/create_instance_helper.py | 7 +-- nova/api/openstack/image_metadata.py | 5 +- nova/api/openstack/images.py | 3 +- nova/api/openstack/ips.py | 11 ++-- nova/api/openstack/server_metadata.py | 1 - nova/api/openstack/servers.py | 92 +++++++++++++--------------- nova/api/openstack/shared_ip_groups.py | 13 ++-- nova/api/openstack/users.py | 3 +- nova/api/openstack/wsgi.py | 6 +- nova/api/openstack/zones.py | 8 +-- 14 files changed, 89 insertions(+), 101 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index e3201b14f..a13a758ab 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -47,10 +47,10 @@ class Controller(object): raise exception.AdminRequired() def index(self, req): - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def detail(self, req): - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def show(self, req, id): """Return data about the given account id""" @@ -65,7 +65,7 @@ class Controller(object): def create(self, req, body): """We use update with create-or-update semantics because the id comes from an external source""" - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def update(self, req, id, body): """This is really create or update.""" diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 3e95aedf3..7ff0d999e 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -19,7 +19,6 @@ import time from webob import exc -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -36,20 +35,20 @@ class Controller(object): def index(self, req, server_id, **kwargs): """ Returns the list of backup schedules for a given instance """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def show(self, req, server_id, id, **kwargs): """ Returns a single backup schedule for a given instance """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create(self, req, server_id, **kwargs): """ No actual update method required, since the existing API allows both create and update through a POST """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id, **kwargs): """ Deletes an existing backup schedule """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create_resource(): diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 8e12ce0c0..f41d25537 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -53,10 +53,10 @@ def get_pagination_params(request): params[param] = int(request.GET[param]) except ValueError: msg = _('%s param must be an integer') % param - raise webob.exc.HTTPBadRequest(msg) + raise webob.exc.HTTPBadRequest(explanation=msg) if params[param] < 0: msg = _('%s param must be positive') % param - raise webob.exc.HTTPBadRequest(msg) + raise webob.exc.HTTPBadRequest(explanation=msg) return params @@ -77,18 +77,22 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit): try: offset = int(request.GET.get('offset', 0)) except ValueError: - raise webob.exc.HTTPBadRequest(_('offset param must be an integer')) + msg = _('offset param must be an integer') + raise webob.exc.HTTPBadRequest(explanation=msg) try: limit = int(request.GET.get('limit', max_limit)) except ValueError: - raise webob.exc.HTTPBadRequest(_('limit param must be an integer')) + msg = _('limit param must be an integer') + raise webob.exc.HTTPBadRequest(explanation=msg) if limit < 0: - raise webob.exc.HTTPBadRequest(_('limit param must be positive')) + msg = _('limit param must be positive') + raise webob.exc.HTTPBadRequest(explanation=msg) if offset < 0: - raise webob.exc.HTTPBadRequest(_('offset param must be positive')) + msg = _('offset param must be positive') + raise webob.exc.HTTPBadRequest(explanation=msg) limit = min(max_limit, limit or max_limit) range_end = offset + limit @@ -111,7 +115,8 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): start_index = i + 1 break if start_index < 0: - raise webob.exc.HTTPBadRequest(_('marker [%s] not found' % marker)) + msg = _('marker [%s] not found' % marker) + raise webob.exc.HTTPBadRequest(explanation=msg) range_end = start_index + limit return items[start_index:range_end] diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 9c7b37f0d..d2655acfa 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -20,7 +20,6 @@ import webob from nova import console from nova import exception -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -72,12 +71,12 @@ class Controller(object): int(server_id), int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return _translate_detail_keys(console) def update(self, req, server_id, id): """You can't update a console""" - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id): """Deletes a console""" @@ -86,7 +85,7 @@ class Controller(object): int(server_id), int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2654e3c40..7784a3e81 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -28,7 +28,6 @@ from nova import quota from nova import utils from nova.compute import instance_types -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova.auth import manager as auth_manager @@ -70,7 +69,7 @@ class CreateInstanceHelper(object): return type from this method is left to the caller. """ if not body: - raise faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() context = req.environ['nova.context'] @@ -94,7 +93,7 @@ class CreateInstanceHelper(object): except Exception, e: msg = _("Cannot find requested image %(image_href)s: %(e)s" % locals()) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) personality = body['server'].get('personality') @@ -153,7 +152,7 @@ class CreateInstanceHelper(object): self._handle_quota_error(error) except exception.ImageNotFound as error: msg = _("Can not find requested image") - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) # Let the caller deal with unhandled exceptions. diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 4f33844fa..c0fc8c09b 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -22,7 +22,6 @@ from nova import flags from nova import image from nova import quota from nova import utils -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -62,7 +61,7 @@ class Controller(object): if id in metadata: return {'meta': {id: metadata[id]}} else: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def create(self, req, image_id, body): context = req.environ['nova.context'] @@ -105,7 +104,7 @@ class Controller(object): img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id) if not id in metadata: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() metadata.pop(id) img['properties'] = metadata self.image_service.update(context, image_id, img, None) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d0317583e..96c5cd3fa 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -25,7 +25,6 @@ from nova import flags import nova.image from nova import log from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import image_metadata from nova.api.openstack import servers from nova.api.openstack.views import images as images_view @@ -78,7 +77,7 @@ class Controller(object): image = self._image_service.show(context, id) except (exception.NotFound, exception.InvalidImageRef): explanation = _("Image not found.") - raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation)) + raise webob.exc.HTTPNotFound(explanation=explanation) return dict(image=self.get_builder(req).build(image, detail=True)) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 1ebfdb831..2996b032d 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -20,7 +20,6 @@ import time from webob import exc import nova -from nova.api.openstack import faults import nova.api.openstack.views.addresses from nova.api.openstack import wsgi from nova import db @@ -37,14 +36,14 @@ class Controller(object): instance = self.compute_api.get( req.environ['nova.context'], server_id) except nova.exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return instance def create(self, req, server_id, body): - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id): - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() class ControllerV10(Controller): @@ -63,7 +62,7 @@ class ControllerV10(Controller): view = builder.build_public_parts(instance) else: msg = _("Only private and public networks available") - return faults.Fault(exc.HTTPNotFound(explanation=msg)) + raise exc.HTTPNotFound(explanation=msg) return {id: view} @@ -86,7 +85,7 @@ class ControllerV11(Controller): if network is None: msg = _("Instance is not a member of specified network") - return faults.Fault(exc.HTTPNotFound(explanation=msg)) + raise exc.HTTPNotFound(explanation=msg) return network diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 3b9169f81..d4f42bbf5 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -18,7 +18,6 @@ from webob import exc from nova import compute -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova import exception from nova import quota diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3b3d0685d..7bef1d9b2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -27,7 +27,6 @@ from nova import log as logging from nova import utils from nova.api.openstack import common from nova.api.openstack import create_instance_helper as helper -from nova.api.openstack import faults import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images @@ -102,17 +101,14 @@ class Controller(object): req.environ['nova.context'], id) return self._build_view(req, instance, is_detail=True) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def create(self, req, body): """ Creates a new server for a given user """ extra_values = None result = None - try: - extra_values, instances = self.helper.create_instance( - req, body, self.compute_api.create) - except faults.Fault, f: - return f + extra_values, instances = self.helper.create_instance( + req, body, self.compute_api.create) # We can only return 1 instance via the API, if we happen to # build more than one... instances is a list, so we'll just @@ -132,7 +128,7 @@ class Controller(object): raise exc.HTTPUnprocessableEntity() if not body: - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() ctxt = req.environ['nova.context'] update_dict = {} @@ -147,7 +143,7 @@ class Controller(object): try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return exc.HTTPNoContent() @@ -171,7 +167,7 @@ class Controller(object): for key in actions.keys(): if key in body: return actions[key](body, req, id) - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def _action_change_password(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -181,7 +177,7 @@ class Controller(object): self.compute_api.confirm_resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in confirm-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return exc.HTTPNoContent() def _action_revert_resize(self, input_dict, req, id): @@ -189,7 +185,7 @@ class Controller(object): self.compute_api.revert_resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in revert-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) def _action_resize(self, input_dict, req, id): @@ -200,14 +196,14 @@ class Controller(object): reboot_type = input_dict['reboot']['type'] else: LOG.exception(_("Missing argument 'type' for reboot")) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver self.compute_api.reboot(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in reboot %s"), e) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) def _action_migrate(self, input_dict, req, id): @@ -215,7 +211,7 @@ class Controller(object): self.compute_api.resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in migrate %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -231,7 +227,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::lock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -247,7 +243,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::unlock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -262,7 +258,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::get_lock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -277,7 +273,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::reset_network %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -292,7 +288,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::inject_network_info %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -304,7 +300,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::pause %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -316,7 +312,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::unpause %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -328,7 +324,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::suspend %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -340,7 +336,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::resume %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -352,7 +348,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::rescue %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -364,7 +360,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::unrescue %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -374,7 +370,7 @@ class Controller(object): self.compute_api.get_ajax_console(req.environ['nova.context'], int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -384,7 +380,7 @@ class Controller(object): self.compute_api.get_vnc_console(req.environ['nova.context'], int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -416,7 +412,7 @@ class ControllerV10(Controller): try: self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) def _image_ref_from_req_data(self, data): @@ -440,17 +436,13 @@ class ControllerV10(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ - try: - if 'resize' in input_dict and 'flavorId' in input_dict['resize']: - flavor_id = input_dict['resize']['flavorId'] - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing 'flavorId' argument for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + if 'resize' in input_dict and 'flavorId' in input_dict['resize']: + flavor_id = input_dict['resize']['flavorId'] + self.compute_api.resize(req.environ['nova.context'], id, + flavor_id) + else: + LOG.exception(_("Missing 'flavorId' argument for resize")) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): @@ -462,14 +454,14 @@ class ControllerV10(Controller): except (KeyError, TypeError): msg = _("Could not parse imageId from request.") LOG.debug(msg) - return faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) try: self.compute_api.rebuild(context, instance_id, image_id) except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) - return faults.Fault(exc.HTTPConflict(explanation=msg)) + raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) @@ -486,7 +478,7 @@ class ControllerV11(Controller): try: self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def _image_ref_from_req_data(self, data): return data['server']['imageRef'] @@ -530,7 +522,7 @@ class ControllerV11(Controller): except AttributeError as ex: msg = _("Unable to parse metadata key/value pairs.") LOG.debug(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) def _decode_personalities(self, personalities): """Decode the Base64-encoded personalities.""" @@ -541,14 +533,14 @@ class ControllerV11(Controller): except (KeyError, TypeError): msg = _("Unable to parse personality path/contents.") LOG.info(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) try: personality["contents"] = base64.b64decode(contents) except TypeError: msg = _("Personality content could not be Base64 decoded.") LOG.info(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ @@ -560,10 +552,10 @@ class ControllerV11(Controller): flavor_id) else: LOG.exception(_("Missing 'flavorRef' argument for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() except Exception, e: LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): @@ -575,7 +567,7 @@ class ControllerV11(Controller): except (KeyError, TypeError): msg = _("Could not parse imageRef from request.") LOG.debug(msg) - return faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) personalities = info["rebuild"].get("personality", []) metadata = info["rebuild"].get("metadata") @@ -591,7 +583,7 @@ class ControllerV11(Controller): except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) - return faults.Fault(exc.HTTPConflict(explanation=msg)) + raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index cf2ddbabb..54d0a8334 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -17,7 +17,6 @@ from webob import exc -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -26,27 +25,27 @@ class Controller(object): def index(self, req, **kwargs): """ Returns a list of Shared IP Groups for the user """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def show(self, req, id, **kwargs): """ Shows in-depth information on a specific Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def update(self, req, id, **kwargs): """ You can't update a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, id, **kwargs): """ Deletes a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def detail(self, req, **kwargs): """ Returns a complete list of Shared IP Groups """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create(self, req, **kwargs): """ Creates a new Shared IP group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create_resource(): diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 6ae1eaf2a..8dd72d559 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -19,7 +19,6 @@ from nova import exception from nova import flags from nova import log as logging from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova.auth import manager @@ -69,7 +68,7 @@ class Controller(object): user = None if user is None: - raise faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return dict(user=_translate_keys(user)) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c3f841aa5..05767da19 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -441,7 +441,11 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) - action_result = self.dispatch(request, action, args) + try: + action_result = self.dispatch(request, action, args) + except webob.exc.HTTPException as ex: + LOG.info(_("HTTP exception thrown: %s"), unicode(ex)) + action_result = faults.Fault(ex) #TODO(bcwaldon): find a more elegant way to pass through non-dict types if type(action_result) is dict or action_result is None: diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 2e02ec380..f7fd87bcd 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -27,7 +27,6 @@ from nova.scheduler import api from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -127,11 +126,8 @@ class Controller(object): Returns a reservation ID (a UUID). """ result = None - try: - extra_values, result = self.helper.create_instance(req, body, - self.compute_api.create_all_at_once) - except faults.Fault, f: - return f + extra_values, result = self.helper.create_instance(req, body, + self.compute_api.create_all_at_once) reservation_id = result return {'reservation_id': reservation_id} -- cgit From 822b5ecede8f18120ae37eb07ff046101f50d3aa Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 11:10:47 -0400 Subject: cleanup tests and fix pep8 issues --- nova/api/openstack/versions.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 49633df88..8e172ae02 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -43,8 +43,8 @@ class Versions(wsgi.Resource): } serializer = wsgi.ResponseSerializer(body_serializers) - supported_content_types = ('application/json', - 'application/xml', + supported_content_types = ('application/json', + 'application/xml', 'application/atom+xml') deserializer = wsgi.RequestDeserializer( supported_content_types=supported_content_types) @@ -73,6 +73,7 @@ class Versions(wsgi.Resource): versions = [builder.build(version) for version in version_objs] return dict(versions=versions) + class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions): root = self._xml_doc.createElement('versions') @@ -96,13 +97,13 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return version_node - def default(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) return self.to_xml_string(node) + class VersionsAtomSerializer(wsgi.XMLDictSerializer): def __init__(self, metadata=None, xmlns=None): if not xmlns: @@ -121,7 +122,8 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def _get_most_recent_update(self, versions): recent = None for version in versions: - updated = datetime.strptime(version['updated'],'%Y-%m-%dT%H:%M:%SZ') + updated = datetime.strptime(version['updated'], + '%Y-%m-%dT%H:%M:%SZ') if not recent: recent = updated elif updated > recent: @@ -132,7 +134,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def _get_base_url(self, link_href): # Make sure no trailing / link_href = link_href.rstrip('/') - return link_href.rsplit('/',1)[0] + '/' + return link_href.rsplit('/', 1)[0] + '/' def _create_meta(self, root, versions): title = self._create_text_elem('title', 'Available API Versions', @@ -164,7 +166,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): entry = self._xml_doc.createElement('entry') id = self._create_text_elem('id', version['links'][0]['href']) - title = self._create_text_elem('title', + title = self._create_text_elem('title', 'Version %s' % version['id'], type='text') updated = self._create_text_elem('updated', version['updated']) @@ -179,9 +181,9 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): link_node.setAttribute('href', link['href']) entry.appendChild(link_node) - content = self._create_text_elem('content', - 'Version %s %s (%s)' % - (version['id'], + content = self._create_text_elem('content', + 'Version %s %s (%s)' % + (version['id'], version['status'], version['updated']), type='text') -- cgit From 9a843b10f3145405ebc01ded5d32ce68d02fdd8d Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 11:56:15 -0400 Subject: pep8 issue --- nova/api/openstack/create_instance_helper.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2ef83332b..10aaa23b1 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -157,8 +157,6 @@ class CreateInstanceHelper(object): except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) - - # Let the caller deal with unhandled exceptions. def _handle_quota_error(self, error): -- cgit From dc616cd633007aa83d7576ae74cf807aa0df6776 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 12:02:01 -0400 Subject: fixed another issue with invalid flavor_id parsing, and added tests --- nova/api/openstack/create_instance_helper.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 10aaa23b1..2b9256896 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -102,7 +102,11 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) - flavor_id = self.controller._flavor_id_from_req_data(body) + try: + flavor_id = self.controller._flavor_id_from_req_data(body) + except ValueError as error: + msg = _("Invalid flavorRef provided.") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) if not 'name' in body['server']: msg = _("Server name is not defined") -- cgit From a57504ef9a8b3e2c5d78cfe4ecf2f21c03653556 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 12:19:09 -0400 Subject: added tests, updated pep8 fixes --- nova/api/openstack/versions.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 8e172ae02..df7a94b7e 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -28,7 +28,6 @@ ATOM_XMLNS = "http://www.w3.org/2005/Atom" class Versions(wsgi.Resource): - def __init__(self): metadata = { "attributes": { -- cgit From 5913e537ceb352ec4e5999cdadb1d826771d5d72 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 14:21:27 -0400 Subject: Updated the compute API so that has_finished_migration uses instance_uuid. Fixes some regressions with 1295-1296. --- nova/api/openstack/views/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index ab7e8da61..7131db088 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -82,7 +82,7 @@ class ViewBuilder(object): ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() - if compute_api.has_finished_migration(ctxt, inst['id']): + if compute_api.has_finished_migration(ctxt, inst['uuid']): inst_dict['status'] = 'RESIZE-CONFIRM' # Return the metadata as a dictionary -- cgit From 5133f92e5c00395b9a9d690e86815aa9c97cda9e Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 14:51:55 -0400 Subject: pep8 and stuff. --- nova/api/openstack/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index f41d25537..57031ebf1 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -77,7 +77,7 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit): try: offset = int(request.GET.get('offset', 0)) except ValueError: - msg = _('offset param must be an integer') + msg = _('offset param must be an integer') raise webob.exc.HTTPBadRequest(explanation=msg) try: @@ -115,7 +115,7 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): start_index = i + 1 break if start_index < 0: - msg = _('marker [%s] not found' % marker) + msg = _('marker [%s] not found') % marker raise webob.exc.HTTPBadRequest(explanation=msg) range_end = start_index + limit return items[start_index:range_end] -- cgit From af5c549d7795fcf9fdcb7d8e9193aa985f121fc6 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 21 Jul 2011 15:08:46 -0400 Subject: Moving lp:~rackspace-titan/nova/extensions-xml-serialization to new branch based off of trunk. To remove dep on another branch. --- nova/api/openstack/extensions.py | 43 +++++++++++++++++++++++++++++++++++----- nova/api/openstack/wsgi.py | 4 ++++ 2 files changed, 42 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index da06ecd15..e500e51fa 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -23,6 +23,7 @@ import sys import routes import webob.dec import webob.exc +from xml.etree import ElementTree from nova import exception from nova import flags @@ -194,7 +195,7 @@ class ExtensionsResource(wsgi.Resource): def show(self, req, id): # NOTE(dprince): the extensions alias is used as the 'id' for show ext = self.extension_manager.extensions[id] - return self._translate(ext) + return dict(extension=self._translate(ext)) def delete(self, req, id): raise faults.Fault(webob.exc.HTTPNotFound()) @@ -258,15 +259,18 @@ class ExtensionMiddleware(base_wsgi.Middleware): mapper = routes.Mapper() + serializer = wsgi.ResponseSerializer( + {'application/xml': ExtensionsXMLSerializer()}) # extended resources for resource in ext_mgr.get_resources(): LOG.debug(_('Extended resource: %s'), resource.collection) mapper.resource(resource.collection, resource.collection, - controller=wsgi.Resource(resource.controller), - collection=resource.collection_actions, - member=resource.member_actions, - parent_resource=resource.parent) + controller=wsgi.Resource( + resource.controller, serializer=serializer), + collection=resource.collection_actions, + member=resource.member_actions, + parent_resource=resource.parent) # extended actions action_resources = self._action_ext_resources(application, ext_mgr, @@ -462,3 +466,32 @@ class ResourceExtension(object): self.parent = parent self.collection_actions = collection_actions self.member_actions = member_actions + + +class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): + + def _add_extension_attributes(self, node, extension): + node.setAttribute('name', extension['name']) + node.setAttribute('namespace', extension['namespace']) + node.setAttribute('alias', extension['alias']) + node.setAttribute('updated', extension['updated']) + + def show(self, ext_dict): + root = ElementTree.Element('extension'); + extension = ext_dict['extension'] + root.set('xmlns', wsgi.XMLNS_V11) + root.set('xmlns:atom', wsgi.XMLNS_ATOM) + root.set('name', extension['name']) + root.set('namespace', extension['namespace']) + root.set('alias', extension['alias']) + root.set('updated', extension['updated']) + desc = ElementTree.Element('description'); + desc.text = extension['description'] + root.append(desc) + for link in extension.get('links', []): + elem = ElementTree.Element('atom:link'); + elem.set('rel', link['rel']) + elem.set('href', link['href']) + elem.set('type', link['type']) + root.append(elem) + return ElementTree.tostring(root) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 9df6fd058..d9993282f 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -13,6 +13,7 @@ from nova import wsgi XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1' +XMLNS_ATOM = 'http://www.w3.org/2005/Atom' LOG = logging.getLogger('nova.api.openstack.wsgi') @@ -352,6 +353,9 @@ class XMLDictSerializer(DictSerializer): link_node = xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) + if link.get('type'): + link_node.setAttribute('type', link['type']) + link_nodes.append(link_node) return link_nodes -- cgit From c1b4dd1b8e3a8043b494854bc9ddd5e6cd335ef0 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 16:16:34 -0400 Subject: Updated time-available to be correct format Fixed old tests to respect this --- nova/api/openstack/views/limits.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index 934b4921a..e34d47e63 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -15,9 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import time from nova.api.openstack import common +from nova import utils class ViewBuilder(object): @@ -113,10 +115,11 @@ class ViewBuilderV11(ViewBuilder): return limits def _build_rate_limit(self, rate_limit): + next_avail = datetime.datetime.fromtimestamp(rate_limit["resetTime"]) return { "verb": rate_limit["verb"], "value": rate_limit["value"], "remaining": int(rate_limit["remaining"]), "unit": rate_limit["unit"], - "next-available": rate_limit["resetTime"], + "next-available": utils.isotime(at=next_avail), } -- cgit From f6514ba9fbb6b3c1939bde6ba081f632b3d0a24f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 17:11:57 -0400 Subject: updated next-available to use utc time --- nova/api/openstack/views/limits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index e34d47e63..c7163e33c 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -115,7 +115,7 @@ class ViewBuilderV11(ViewBuilder): return limits def _build_rate_limit(self, rate_limit): - next_avail = datetime.datetime.fromtimestamp(rate_limit["resetTime"]) + next_avail = datetime.datetime.utcfromtimestamp(rate_limit["resetTime"]) return { "verb": rate_limit["verb"], "value": rate_limit["value"], -- cgit From fe708ae810efd17da70659076faa9c2f3b544d3b Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 17:13:12 -0400 Subject: pep8 --- nova/api/openstack/views/limits.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index c7163e33c..f603d7cb4 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -115,7 +115,8 @@ class ViewBuilderV11(ViewBuilder): return limits def _build_rate_limit(self, rate_limit): - next_avail = datetime.datetime.utcfromtimestamp(rate_limit["resetTime"]) + next_avail = \ + datetime.datetime.utcfromtimestamp(rate_limit["resetTime"]) return { "verb": rate_limit["verb"], "value": rate_limit["value"], -- cgit From 5f75097eb46fa03814fe53c5d9fda84f0000fdd4 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 21 Jul 2011 22:46:57 +0000 Subject: start removing references to AuthManager --- nova/api/direct.py | 3 ++- nova/api/ec2/__init__.py | 10 ++++++---- nova/api/openstack/auth.py | 26 +++++++------------------- 3 files changed, 15 insertions(+), 24 deletions(-) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index ec79151b1..993815fc7 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -107,7 +107,8 @@ class DelegatedAuthMiddleware(wsgi.Middleware): def process_request(self, request): os_user = request.headers['X-OpenStack-User'] os_project = request.headers['X-OpenStack-Project'] - context_ref = context.RequestContext(user=os_user, project=os_project) + context_ref = context.RequestContext(user_id=os_user, + project_id=os_project) request.environ['openstack.context'] = context_ref diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index cf1734281..8bb2ea944 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -174,8 +174,8 @@ class Authenticate(wsgi.Middleware): remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) - ctxt = context.RequestContext(user=user, - project=project, + ctxt = context.RequestContext(user_id=user.id, + project_id=project.id, remote_address=remote_address) req.environ['ec2.context'] = ctxt uname = user.name @@ -295,13 +295,15 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" - if context.user.is_superuser(): + authman = manager.AuthManager() + user = authman.get_user(context.user_id) + if user.is_superuser(): return True if 'all' in roles: return True if 'none' in roles: return False - return any(context.project.has_role(context.user_id, role) + return any(authman.has_role(context.user_id, role, context.project_id) for role in roles) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 7c3e683d6..5b387c081 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -48,31 +48,19 @@ class AuthMiddleware(wsgi.Middleware): def __call__(self, req): if not self.has_authentication(req): return self.authenticate(req) - user = self.get_user_by_authentication(req) - if not user: + user_id = self.get_user_by_authentication(req) + if not user_id: token = req.headers["X-Auth-Token"] - msg = _("%(user)s could not be found with token '%(token)s'") + msg = _("%(user_id)s could not be found with token '%(token)s'") LOG.warn(msg % locals()) return faults.Fault(webob.exc.HTTPUnauthorized()) try: - account = req.headers["X-Auth-Project-Id"] + project_id = req.headers["X-Auth-Project-Id"] except KeyError: - # FIXME(usrleon): It needed only for compatibility - # while osapi clients don't use this header - accounts = self.auth.get_projects(user=user) - if accounts: - account = accounts[0] - else: - return faults.Fault(webob.exc.HTTPUnauthorized()) - - if not self.auth.is_admin(user) and \ - not self.auth.is_project_member(user, account): - msg = _("%(user)s must be an admin or a member of %(account)s") - LOG.warn(msg % locals()) - return faults.Fault(webob.exc.HTTPUnauthorized()) + project_id = user_id - req.environ['nova.context'] = context.RequestContext(user, account) + req.environ['nova.context'] = context.RequestContext(user_id, project_id) return self.application def has_authentication(self, req): @@ -133,7 +121,7 @@ class AuthMiddleware(wsgi.Middleware): if delta.days >= 2: self.db.auth_token_destroy(ctxt, token['token_hash']) else: - return self.auth.get_user(token['user_id']) + return token['user_id'] return None def _authorize_user(self, username, key, req): -- cgit From e1cf345fa82c3a9b8088237f1025c41db0f4e829 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 00:39:53 +0000 Subject: fix a whole bunch of tests --- nova/api/ec2/__init__.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 8bb2ea944..edae94331 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -138,8 +138,19 @@ class Lockout(wsgi.Middleware): return res -class Authenticate(wsgi.Middleware): +class InjectContext(wsgi.Middleware): + """Always add a fake 'ec2.context' to WSGI environ.""" + def __init__(self, context, *args, **kwargs): + self.context = context + super(InjectContext, self).__init__(*args, **kwargs) + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + req.environ['ec2.context'] = self.context + return self.application + + +class Authenticate(wsgi.Middleware): """Authenticate an EC2 request and add 'ec2.context' to WSGI environ.""" @webob.dec.wsgify(RequestClass=wsgi.Request) @@ -295,16 +306,13 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" - authman = manager.AuthManager() - user = authman.get_user(context.user_id) - if user.is_superuser(): + if context.is_admin: return True if 'all' in roles: return True if 'none' in roles: return False - return any(authman.has_role(context.user_id, role, context.project_id) - for role in roles) + return any(role in context.roles for role in roles) class Executor(wsgi.Application): -- cgit From 16aa079b933a8788c926745e0794d85558d442a8 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 21 Jul 2011 22:35:26 -0400 Subject: Added xml serialization for GET => /extensions. Added corresponding tests. --- nova/api/openstack/extensions.py | 43 +++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 20 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index e500e51fa..ad03a6ac9 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -470,28 +470,31 @@ class ResourceExtension(object): class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): - def _add_extension_attributes(self, node, extension): - node.setAttribute('name', extension['name']) - node.setAttribute('namespace', extension['namespace']) - node.setAttribute('alias', extension['alias']) - node.setAttribute('updated', extension['updated']) - - def show(self, ext_dict): - root = ElementTree.Element('extension'); - extension = ext_dict['extension'] - root.set('xmlns', wsgi.XMLNS_V11) - root.set('xmlns:atom', wsgi.XMLNS_ATOM) - root.set('name', extension['name']) - root.set('namespace', extension['namespace']) - root.set('alias', extension['alias']) - root.set('updated', extension['updated']) + def _create_ext_elem(self, ext_dict): + ext_elem = ElementTree.Element('extension'); + ext_elem.set('xmlns', wsgi.XMLNS_V11) + ext_elem.set('xmlns:atom', wsgi.XMLNS_ATOM) + ext_elem.set('name', ext_dict['name']) + ext_elem.set('namespace', ext_dict['namespace']) + ext_elem.set('alias', ext_dict['alias']) + ext_elem.set('updated', ext_dict['updated']) desc = ElementTree.Element('description'); - desc.text = extension['description'] - root.append(desc) - for link in extension.get('links', []): + desc.text = ext_dict['description'] + ext_elem.append(desc) + for link in ext_dict.get('links', []): elem = ElementTree.Element('atom:link'); elem.set('rel', link['rel']) elem.set('href', link['href']) elem.set('type', link['type']) - root.append(elem) - return ElementTree.tostring(root) + ext_elem.append(elem) + return ext_elem + + def show(self, ext_dict): + ext = self._create_ext_elem(ext_dict['extension']) + return ElementTree.tostring(ext, encoding='UTF-8') + + def index(self, exts_dict): + exts = ElementTree.Element('extensions'); + for ext_dict in exts_dict['extensions']: + exts.append(self._create_ext_elem(ext_dict)) + return ElementTree.tostring(exts, encoding='UTF-8') -- cgit From c83546c0c9d077fb69caf1d782e8cb420e399a20 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 21 Jul 2011 23:36:40 -0400 Subject: Correctly add xml namespaces to extensions xml. --- nova/api/openstack/extensions.py | 33 +++++++++++++++++++-------------- nova/api/openstack/wsgi.py | 1 - 2 files changed, 19 insertions(+), 15 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index ad03a6ac9..cc889703e 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -470,31 +470,36 @@ class ResourceExtension(object): class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): + def show(self, ext_dict): + ext = self._create_ext_elem(ext_dict['extension']) + return self._to_xml(ext) + + def index(self, exts_dict): + exts = ElementTree.Element('extensions') + for ext_dict in exts_dict['extensions']: + exts.append(self._create_ext_elem(ext_dict)) + return self._to_xml(exts) + def _create_ext_elem(self, ext_dict): - ext_elem = ElementTree.Element('extension'); - ext_elem.set('xmlns', wsgi.XMLNS_V11) - ext_elem.set('xmlns:atom', wsgi.XMLNS_ATOM) + """Create an extension xml element from a dict.""" + ext_elem = ElementTree.Element('extension') ext_elem.set('name', ext_dict['name']) ext_elem.set('namespace', ext_dict['namespace']) ext_elem.set('alias', ext_dict['alias']) ext_elem.set('updated', ext_dict['updated']) - desc = ElementTree.Element('description'); + desc = ElementTree.Element('description') desc.text = ext_dict['description'] ext_elem.append(desc) for link in ext_dict.get('links', []): - elem = ElementTree.Element('atom:link'); + elem = ElementTree.Element('atom:link') elem.set('rel', link['rel']) elem.set('href', link['href']) elem.set('type', link['type']) ext_elem.append(elem) return ext_elem - def show(self, ext_dict): - ext = self._create_ext_elem(ext_dict['extension']) - return ElementTree.tostring(ext, encoding='UTF-8') - - def index(self, exts_dict): - exts = ElementTree.Element('extensions'); - for ext_dict in exts_dict['extensions']: - exts.append(self._create_ext_elem(ext_dict)) - return ElementTree.tostring(exts, encoding='UTF-8') + def _to_xml(self, root): + """Convert the xml tree object to an xml string.""" + root.set('xmlns', wsgi.XMLNS_V11) + root.set('xmlns:atom', wsgi.XMLNS_ATOM) + return ElementTree.tostring(root, encoding='UTF-8') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index d9993282f..ee1572c4f 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -355,7 +355,6 @@ class XMLDictSerializer(DictSerializer): link_node.setAttribute('href', link['href']) if link.get('type'): link_node.setAttribute('type', link['type']) - link_nodes.append(link_node) return link_nodes -- cgit From 44d1024a53b8150cf9542d08d5886f430365f161 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 19:47:41 +0000 Subject: fix all tests --- nova/api/ec2/__init__.py | 29 +++++++++-------------------- nova/api/openstack/auth.py | 20 ++++++++++++++++++-- 2 files changed, 27 insertions(+), 22 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index edae94331..0a743075c 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -66,7 +66,7 @@ class RequestLogging(wsgi.Middleware): else: controller = None action = None - ctxt = request.environ.get('ec2.context', None) + ctxt = request.environ.get('nova.context', None) delta = utils.utcnow() - start seconds = delta.seconds microseconds = delta.microseconds @@ -138,20 +138,8 @@ class Lockout(wsgi.Middleware): return res -class InjectContext(wsgi.Middleware): - """Always add a fake 'ec2.context' to WSGI environ.""" - def __init__(self, context, *args, **kwargs): - self.context = context - super(InjectContext, self).__init__(*args, **kwargs) - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - req.environ['ec2.context'] = self.context - return self.application - - class Authenticate(wsgi.Middleware): - """Authenticate an EC2 request and add 'ec2.context' to WSGI environ.""" + """Authenticate an EC2 request and add 'nova.context' to WSGI environ.""" @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): @@ -187,12 +175,13 @@ class Authenticate(wsgi.Middleware): remote_address = req.headers.get('X-Forwarded-For', remote_address) ctxt = context.RequestContext(user_id=user.id, project_id=project.id, + is_admin=user.is_admin(), remote_address=remote_address) - req.environ['ec2.context'] = ctxt + req.environ['nova.context'] = ctxt uname = user.name pname = project.name msg = _('Authenticated Request For %(uname)s:%(pname)s)') % locals() - LOG.audit(msg, context=req.environ['ec2.context']) + LOG.audit(msg, context=req.environ['nova.context']) return self.application @@ -239,7 +228,7 @@ class Authorizer(wsgi.Middleware): """Authorize an EC2 API request. Return a 401 if ec2.controller and ec2.action in WSGI environ may not be - executed in ec2.context. + executed in nova.context. """ def __init__(self, application): @@ -293,7 +282,7 @@ class Authorizer(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - context = req.environ['ec2.context'] + context = req.environ['nova.context'] controller = req.environ['ec2.request'].controller.__class__.__name__ action = req.environ['ec2.request'].action allowed_roles = self.action_roles[controller].get(action, ['none']) @@ -319,14 +308,14 @@ class Executor(wsgi.Application): """Execute an EC2 API request. - Executes 'ec2.action' upon 'ec2.controller', passing 'ec2.context' and + Executes 'ec2.action' upon 'ec2.controller', passing 'nova.context' and 'ec2.action_args' (all variables in WSGI environ.) Returns an XML response, or a 400 upon failure. """ @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - context = req.environ['ec2.context'] + context = req.environ['nova.context'] api_request = req.environ['ec2.request'] result = None try: diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 5b387c081..9caa14a4e 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -58,9 +58,25 @@ class AuthMiddleware(wsgi.Middleware): try: project_id = req.headers["X-Auth-Project-Id"] except KeyError: - project_id = user_id + # FIXME(usrleon): It needed only for compatibility + # while osapi clients don't use this header + projects = self.auth.get_projects(user_id) + if projects: + project_id = projects[0] + else: + return faults.Fault(webob.exc.HTTPUnauthorized()) + + is_admin = self.auth.is_admin(user_id) + req.environ['nova.context'] = context.RequestContext(user_id, + project_id, + is_admin) + if not is_admin and not self.auth.is_project_member(user_id, + project_id): + msg = _("%(user_id)s must be an admin or a " + "member of %(project_id)s") + LOG.warn(msg % locals()) + return faults.Fault(webob.exc.HTTPUnauthorized()) - req.environ['nova.context'] = context.RequestContext(user_id, project_id) return self.application def has_authentication(self, req): -- cgit From 0f8eee7ff32a91c866742939b1f551f3610f1276 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 20:20:31 +0000 Subject: fix auth tests --- nova/api/openstack/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 9caa14a4e..d42abe1f8 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -62,7 +62,7 @@ class AuthMiddleware(wsgi.Middleware): # while osapi clients don't use this header projects = self.auth.get_projects(user_id) if projects: - project_id = projects[0] + project_id = projects[0].id else: return faults.Fault(webob.exc.HTTPUnauthorized()) -- cgit From e8defa6bdd5af85486d0d3acce8956670ca16882 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 20:41:46 +0000 Subject: fix test_access --- nova/api/ec2/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 0a743075c..1ea26fdeb 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -156,8 +156,9 @@ class Authenticate(wsgi.Middleware): auth_params.pop('Signature') # Authenticate the request. + authman = manager.AuthManager() try: - (user, project) = manager.AuthManager().authenticate( + (user, project) = authman.authenticate( access, signature, auth_params, @@ -173,9 +174,12 @@ class Authenticate(wsgi.Middleware): remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) + roles = authman.get_active_roles(user, project) + LOG.warn(roles) ctxt = context.RequestContext(user_id=user.id, project_id=project.id, is_admin=user.is_admin(), + roles=roles, remote_address=remote_address) req.environ['nova.context'] = ctxt uname = user.name @@ -295,6 +299,7 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" + LOG.info(context.roles) if context.is_admin: return True if 'all' in roles: -- cgit From faf71d498e9e98e60e65be94c7e306fc7b4e4f98 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 22 Jul 2011 16:54:25 -0400 Subject: updating images metadata resource --- nova/api/openstack/__init__.py | 11 ++++-- nova/api/openstack/create_instance_helper.py | 45 ++++------------------- nova/api/openstack/image_metadata.py | 53 ++++++++++++++++++++++++++-- nova/api/openstack/wsgi.py | 52 +++++++++++++++++++++------ 4 files changed, 108 insertions(+), 53 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index e87d7c754..9242c2c29 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -164,10 +164,17 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper, '1.1') - mapper.resource("image_meta", "meta", - controller=image_metadata.create_resource(), + image_metadata_controller = image_metadata.create_resource() + mapper.resource("image_meta", "metadata", + controller=image_metadata_controller, parent_resource=dict(member_name='image', collection_name='images')) + mapper.connect("image_meta", controller=image_metadata_controller, + action='update_collection', condition={"method":['PUT']}) + mapper.connect("metadata", "/images/{image_id}/metadata", + controller=image_metadata_controller, + action='update_all', + conditions={"method":['PUT',]}) mapper.resource("server_meta", "meta", controller=server_metadata.create_resource(), diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 7249f1261..49e46f663 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -282,7 +282,7 @@ class CreateInstanceHelper(object): return password -class ServerXMLDeserializer(wsgi.XMLDeserializer): +class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): """ Deserializer to handle xml-formatted server create requests. @@ -299,11 +299,12 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): def _extract_server(self, node): """Marshal the server attribute of a parsed request""" server = {} - server_node = self._find_first_child_named(node, 'server') + server_node = self.find_first_child_named(node, 'server') for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) - metadata = self._extract_metadata(server_node) + metadata_node = self.find_first_child_named(server_node, "metadata") + metadata = self.extract_metadata(metadata_node) if metadata is not None: server["metadata"] = metadata personality = self._extract_personality(server_node) @@ -311,49 +312,17 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): server["personality"] = personality return server - def _extract_metadata(self, server_node): - """Marshal the metadata attribute of a parsed request""" - metadata_node = self._find_first_child_named(server_node, "metadata") - if metadata_node is None: - return None - metadata = {} - for meta_node in self._find_children_named(metadata_node, "meta"): - key = meta_node.getAttribute("key") - metadata[key] = self._extract_text(meta_node) - return metadata - def _extract_personality(self, server_node): """Marshal the personality attribute of a parsed request""" personality_node = \ - self._find_first_child_named(server_node, "personality") + self.find_first_child_named(server_node, "personality") if personality_node is None: return None personality = [] - for file_node in self._find_children_named(personality_node, "file"): + for file_node in self.find_children_named(personality_node, "file"): item = {} if file_node.hasAttribute("path"): item["path"] = file_node.getAttribute("path") - item["contents"] = self._extract_text(file_node) + item["contents"] = self.extract_text(file_node) personality.append(item) return personality - - def _find_first_child_named(self, parent, name): - """Search a nodes children for the first child with a given name""" - for node in parent.childNodes: - if node.nodeName == name: - return node - return None - - def _find_children_named(self, parent, name): - """Return all of a nodes children who have the given name""" - for node in parent.childNodes: - if node.nodeName == name: - yield node - - def _extract_text(self, node): - """Get the text field contained by the given node""" - if len(node.childNodes) == 1: - child = node.childNodes[0] - if child.nodeType == child.TEXT_NODE: - return child.nodeValue - return "" diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index c0fc8c09b..ee181c924 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -96,8 +96,16 @@ class Controller(object): self._check_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) + return dict(meta=meta) - return req.body + def update_all(self, req, image_id, body): + context = req.environ['nova.context'] + img = self.image_service.show(context, image_id) + metadata = body.get('metadata', {}) + self._check_quota_limit(context, metadata) + img['properties'] = metadata + self.image_service.update(context, image_id, img, None) + return dict(metadata=metadata) def delete(self, req, image_id, id): context = req.environ['nova.context'] @@ -110,6 +118,32 @@ class Controller(object): self.image_service.update(context, image_id, img, None) +class ImageMetadataXMLDeserializer(wsgi.MetadataXMLDeserializer): + + def _extract_metadata_container(self, datastring): + dom = minidom.parseString(datastring) + metadata_node = self.find_first_child_named(dom, "metadata") + metadata = self.extract_metadata(metadata_node) + return {'body': {'metadata': metadata}} + + def create(self, datastring): + return self._extract_metadata_container(datastring) + + def update_all(self, datastring): + return self._extract_metadata_container(datastring) + + def update(self, datastring): + dom = minidom.parseString(datastring) + metadata_item = self.extract_metadata(dom) + return {'body': {'meta': metadata_item}} + + +class HeadersSerializer(wsgi.ResponseHeadersSerializer): + + def delete(self, response, data): + response.status_int = 204 + + class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def __init__(self, xmlns=wsgi.XMLNS_V11): super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns) @@ -143,6 +177,9 @@ class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def create(self, metadata_dict): return self._meta_list_to_xml_string(metadata_dict) + def update_all(self, metadata_dict): + return self._meta_list_to_xml_string(metadata_dict) + def _meta_item_to_xml_string(self, meta_item_dict): xml_doc = minidom.Document() item_key, item_value = meta_item_dict.items()[0] @@ -157,11 +194,21 @@ class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def update(self, meta_item_dict): return self._meta_item_to_xml_string(meta_item_dict['meta']) + def default(self, *args, **kwargs): + return '' + def create_resource(): + headers_serializer = HeadersSerializer() + + body_deserializers = { + 'application/xml': ImageMetadataXMLDeserializer(), + } + body_serializers = { 'application/xml': ImageMetadataXMLSerializer(), } - serializer = wsgi.ResponseSerializer(body_serializers) + serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) + deserializer = wsgi.RequestDeserializer(body_deserializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(Controller(), deserializer, serializer) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 1e5a5143d..0463020c8 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -135,10 +135,44 @@ class XMLDeserializer(TextDeserializer): listnames) return result + def find_first_child_named(self, parent, name): + """Search a nodes children for the first child with a given name""" + for node in parent.childNodes: + if node.nodeName == name: + return node + return None + + def find_children_named(self, parent, name): + """Return all of a nodes children who have the given name""" + for node in parent.childNodes: + if node.nodeName == name: + yield node + + def extract_text(self, node): + """Get the text field contained by the given node""" + if len(node.childNodes) == 1: + child = node.childNodes[0] + if child.nodeType == child.TEXT_NODE: + return child.nodeValue + return "" + def default(self, datastring): return {'body': self._from_xml(datastring)} +class MetadataXMLDeserializer(XMLDeserializer): + + def extract_metadata(self, metadata_node): + """Marshal the metadata attribute of a parsed request""" + if metadata_node is None: + return None + metadata = {} + for meta_node in self.find_children_named(metadata_node, "meta"): + key = meta_node.getAttribute("key") + metadata[key] = self.extract_text(meta_node) + return metadata + + class RequestHeadersDeserializer(ActionDispatcher): """Default request headers deserializer""" @@ -395,9 +429,10 @@ class ResponseSerializer(object): self.headers_serializer.serialize(response, data, action) def serialize_body(self, response, data, content_type, action): - response.headers['Content-Type'] = content_type - serializer = self.get_body_serializer(content_type) - response.body = serializer.serialize(data, action) + if data is not None: + response.headers['Content-Type'] = content_type + serializer = self.get_body_serializer(content_type) + response.body = serializer.serialize(data, action) def get_body_serializer(self, content_type): try: @@ -443,7 +478,7 @@ class Resource(wsgi.Application): action, args, accept = self.deserializer.deserialize(request) except exception.InvalidContentType: msg = _("Unsupported Content-Type") - return webob.exc.HTTPBadRequest(explanation=msg) + return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) except exception.MalformedRequestBody: msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) @@ -452,13 +487,10 @@ class Resource(wsgi.Application): action_result = self.dispatch(request, action, args) except webob.exc.HTTPException as ex: LOG.info(_("HTTP exception thrown: %s"), unicode(ex)) - action_result = faults.Fault(ex) + return faults.Fault(ex) - #TODO(bcwaldon): find a more elegant way to pass through non-dict types - if type(action_result) is dict or action_result is None: - response = self.serializer.serialize(action_result, - accept, - action=action) + if isinstance(action_result, dict) or action_result is None: + response = self.serializer.serialize(action_result, accept, action) else: response = action_result -- cgit From bc800b16cf304811802d1e441823cffff610fc6f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 22 Jul 2011 17:04:26 -0400 Subject: pep8 --- nova/api/openstack/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 9242c2c29..868b98a31 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -169,12 +169,11 @@ class APIRouterV11(APIRouter): controller=image_metadata_controller, parent_resource=dict(member_name='image', collection_name='images')) - mapper.connect("image_meta", controller=image_metadata_controller, - action='update_collection', condition={"method":['PUT']}) + mapper.connect("metadata", "/images/{image_id}/metadata", controller=image_metadata_controller, action='update_all', - conditions={"method":['PUT',]}) + conditions={"method": ['PUT']}) mapper.resource("server_meta", "meta", controller=server_metadata.create_resource(), -- cgit From ccb5119280d341a2ea1b3e8352acbf32b7f243af Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 21:36:41 +0000 Subject: clean up fake auth manager in other places --- nova/api/ec2/__init__.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 1ea26fdeb..af232edda 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -175,7 +175,6 @@ class Authenticate(wsgi.Middleware): if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) roles = authman.get_active_roles(user, project) - LOG.warn(roles) ctxt = context.RequestContext(user_id=user.id, project_id=project.id, is_admin=user.is_admin(), @@ -299,7 +298,6 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" - LOG.info(context.roles) if context.is_admin: return True if 'all' in roles: -- cgit From 164afd51017721b9cbaf2880b9dada3d4cd9b42c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 22:04:52 +0000 Subject: remove auth manager from instance helper --- nova/api/openstack/create_instance_helper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 7249f1261..2034e8ada 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -20,6 +20,7 @@ import webob from webob import exc from xml.dom import minidom +from nova import db from nova import exception from nova import flags from nova import log as logging @@ -29,7 +30,6 @@ from nova import utils from nova.compute import instance_types from nova.api.openstack import wsgi -from nova.auth import manager as auth_manager LOG = logging.getLogger('nova.api.openstack.create_instance_helper') @@ -77,7 +77,8 @@ class CreateInstanceHelper(object): key_name = None key_data = None - key_pairs = auth_manager.AuthManager.get_key_pairs(context) + key_pairs = db.key_pair_get_all_by_user(context.elevated(), + context.user_id) if key_pairs: key_pair = key_pairs[0] key_name = key_pair['name'] -- cgit From 8501cc95aa60a0a5759cf911e8adaf624fa9e547 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 25 Jul 2011 12:02:26 -0400 Subject: removing unnecessary assignments --- nova/api/openstack/servers.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1e09b6ea0..131937422 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -454,7 +454,6 @@ class ControllerV10(Controller): def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] - instance_id = instance_id try: image_id = info["rebuild"]["imageId"] @@ -569,7 +568,6 @@ class ControllerV11(Controller): def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] - instance_id = instance_id try: image_href = info["rebuild"]["imageRef"] -- cgit From a158166a1148a1ea35a04fb25b10361d86f36138 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 25 Jul 2011 12:08:49 -0400 Subject: Updated Faults controller to choose an xml serializer based on api version found in the request url --- nova/api/openstack/common.py | 28 ++++++++++++++++++++++++++++ nova/api/openstack/faults.py | 17 +++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 57031ebf1..97c9de167 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -167,3 +167,31 @@ def remove_version_from_href(href): msg = _('href does not contain version') raise ValueError(msg) return new_href + + +def get_version_from_href(href): + """Returns the api version in the href. + + Returns the api version in the href. + If no version is found, 1.0 is returned + + Given: 'http://www.nova.com/123' + Returns: '1.0' + + Given: 'http://www.nova.com/v1.1' + Returns: '1.1' + + """ + try: + #finds the first instance that matches /v#.#/ + version = re.findall(r'[/][v][0-9]+\.[0-9]+[/]', href) + #if no version was found, try finding /v#.# at the end of the string + if not version: + version = re.findall(r'[/][v][0-9]+\.[0-9]+$', href) + print href + print " " + print version + version = re.findall(r'[0-9]+\.[0-9]', version[0])[0] + except IndexError: + version = '1.0' + return version diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 24cde69e4..839b33dfe 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -19,6 +19,7 @@ import webob.dec import webob.exc +from nova.api.openstack import common from nova.api.openstack import wsgi @@ -61,9 +62,13 @@ class Fault(webob.exc.HTTPException): content_type = req.best_match_content_type() + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), + '1.1': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V11), + }[common.get_version_from_href(req.url)] + serializer = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), + 'application/xml': xml_serializer, 'application/json': wsgi.JSONDictSerializer(), }[content_type] @@ -100,9 +105,13 @@ class OverLimitFault(webob.exc.HTTPException): content_type = request.best_match_content_type() metadata = {"attributes": {"overLimitFault": "code"}} + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), + '1.1': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V11), + }[common.get_version_from_href(req.url)] + serializer = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), + 'application/xml': xml_serializer, 'application/json': wsgi.JSONDictSerializer(), }[content_type] -- cgit From e24988c3b154d188ac69030d0b1d2811fb91d2e7 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 25 Jul 2011 13:28:22 -0400 Subject: Fixed bad test Fixed using wrong variable --- nova/api/openstack/faults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 839b33dfe..1ab45d4f1 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -108,7 +108,7 @@ class OverLimitFault(webob.exc.HTTPException): xml_serializer = { '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), '1.1': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V11), - }[common.get_version_from_href(req.url)] + }[common.get_version_from_href(request.url)] serializer = { 'application/xml': xml_serializer, -- cgit From 8de3c0fcaee546fae3d415ef5ddcbb51fb1db6d7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 25 Jul 2011 17:49:09 +0000 Subject: fix for reviews --- nova/api/openstack/create_instance_helper.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2034e8ada..573153f68 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -77,6 +77,8 @@ class CreateInstanceHelper(object): key_name = None key_data = None + # TODO(vish): Key pair access should move into a common library + # instead of being accessed directly from the db. key_pairs = db.key_pair_get_all_by_user(context.elevated(), context.user_id) if key_pairs: -- cgit From 99bc14f16bce9f125715fbe436b7fc0969b62420 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 25 Jul 2011 14:10:08 -0400 Subject: added 1.0 detail test, added VersionRequestDeserializer to support Versions actions properly, started 300/multiple choice work --- nova/api/openstack/__init__.py | 6 +++ nova/api/openstack/versions.py | 92 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index e87d7c754..fb6f5515e 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -40,6 +40,7 @@ from nova.api.openstack import servers from nova.api.openstack import server_metadata from nova.api.openstack import shared_ip_groups from nova.api.openstack import users +from nova.api.openstack import versions from nova.api.openstack import wsgi from nova.api.openstack import zones @@ -115,6 +116,10 @@ class APIRouter(base_wsgi.Router): 'select': 'POST', 'boot': 'POST'}) + mapper.connect("versions", "/", + controller=versions.create_resource(version), + action="index") + mapper.resource("console", "consoles", controller=consoles.create_resource(), parent_resource=dict(member_name='server', @@ -164,6 +169,7 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper, '1.1') + mapper.resource("image_meta", "meta", controller=image_metadata.create_resource(), parent_resource=dict(member_name='image', diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index df7a94b7e..445a14372 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -28,6 +28,13 @@ ATOM_XMLNS = "http://www.w3.org/2005/Atom" class Versions(wsgi.Resource): + @classmethod + def factory(cls, global_config, **local_config): + """Paste factory.""" + def _factory(app): + return cls(app, **local_config) + return _factory + def __init__(self): metadata = { "attributes": { @@ -45,7 +52,7 @@ class Versions(wsgi.Resource): supported_content_types = ('application/json', 'application/xml', 'application/atom+xml') - deserializer = wsgi.RequestDeserializer( + deserializer = VersionsRequestDeserializer( supported_content_types=supported_content_types) wsgi.Resource.__init__(self, None, serializer=serializer, @@ -53,6 +60,14 @@ class Versions(wsgi.Resource): def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" + if request.path == '/': + # List Versions + return self._versions_list(request) + else: + # Versions Multiple Choice + return self._versions_multi_choice(request) + + def _versions_list(self, request): version_objs = [ { "id": "v1.1", @@ -72,6 +87,47 @@ class Versions(wsgi.Resource): versions = [builder.build(version) for version in version_objs] return dict(versions=versions) + def _versions_multi_choice(self, request): + version_objs = [ + { + "id": "v1.1", + "status": "CURRENT", + #TODO(wwolf) get correct value for these + "updated": "2011-07-18T11:30:00Z", + }, + { + "id": "v1.0", + "status": "DEPRECATED", + #TODO(wwolf) get correct value for these + "updated": "2010-10-09T11:30:00Z", + }, + ] + + builder = nova.api.openstack.views.versions.get_view_builder(request) + versions = [builder.build(version) for version in version_objs] + return dict(versions=versions) + + +class VersionV10(object): + def index(self, req): + return "test index 1.0" + + +class VersionV11(object): + def index(self, req): + return "test index 1.1" + +class VersionsRequestDeserializer(wsgi.RequestDeserializer): + def get_action_args(self, request_environment): + """Parse dictionary created by routes library.""" + + args = {} + if request_environment['PATH_INFO'] == '/': + args['action'] = 'index' + else: + args['action'] = 'multi' + + return args class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions): @@ -96,7 +152,13 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return version_node - def default(self, data): + def index(self, data): + self._xml_doc = minidom.Document() + node = self._versions_to_xml(data['versions']) + + return self.to_xml_string(node) + + def multi(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) @@ -190,10 +252,34 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): entry.appendChild(content) root.appendChild(entry) - def default(self, data): + def index(self, data): self._xml_doc = minidom.Document() node = self._xml_doc.createElementNS(self.xmlns, 'feed') self._create_meta(node, data['versions']) self._create_version_entries(node, data['versions']) return self.to_xml_string(node) + + def multi(self, data): + self._xml_doc = minidom.Document() + node = self._xml_doc.createElementNS(self.xmlns, 'feed') + self._create_meta(node, data['versions']) + self._create_version_entries(node, data['versions']) + + return self.to_xml_string(node) + + +def create_resource(version='1.0'): + controller = { + '1.0': VersionV10, + '1.1': VersionV11, + }[version]() + + body_serializers = { + 'application/xml': VersionsXMLSerializer(), + 'application/atom+xml': VersionsAtomSerializer(), + } + + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(controller, serializer=serializer) -- cgit From bdd70a8c5e0aea3c1722817809c34cc78fee3ab9 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 25 Jul 2011 14:24:59 -0400 Subject: removed print lines --- nova/api/openstack/common.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 97c9de167..bd14a1389 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -188,9 +188,6 @@ def get_version_from_href(href): #if no version was found, try finding /v#.# at the end of the string if not version: version = re.findall(r'[/][v][0-9]+\.[0-9]+$', href) - print href - print " " - print version version = re.findall(r'[0-9]+\.[0-9]', version[0])[0] except IndexError: version = '1.0' -- cgit From 28cc235edebf1986102cb51bebaacfa0c0bad984 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 25 Jul 2011 15:08:26 -0400 Subject: reverting some wsgi-related changes --- nova/api/openstack/wsgi.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 0463020c8..7842b821a 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -487,10 +487,12 @@ class Resource(wsgi.Application): action_result = self.dispatch(request, action, args) except webob.exc.HTTPException as ex: LOG.info(_("HTTP exception thrown: %s"), unicode(ex)) - return faults.Fault(ex) + action_result = faults.Fault(ex) - if isinstance(action_result, dict) or action_result is None: - response = self.serializer.serialize(action_result, accept, action) + if type(action_result) is dict or action_result is None: + response = self.serializer.serialize(action_result, + accept, + action=action) else: response = action_result -- cgit From 810d4b89cbbfa9388fb61f9069ea0104a7d77752 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 25 Jul 2011 15:28:06 -0400 Subject: removed prints, got versions detail tests passing, still need to do xml/atom --- nova/api/openstack/__init__.py | 2 +- nova/api/openstack/versions.py | 96 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 84 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index fb6f5515e..a6a8b4595 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -118,7 +118,7 @@ class APIRouter(base_wsgi.Router): mapper.connect("versions", "/", controller=versions.create_resource(version), - action="index") + action="detail") mapper.resource("console", "consoles", controller=consoles.create_resource(), diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 445a14372..5655edcfc 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -88,6 +88,7 @@ class Versions(wsgi.Resource): return dict(versions=versions) def _versions_multi_choice(self, request): + #TODO version_objs = [ { "id": "v1.1", @@ -109,18 +110,87 @@ class Versions(wsgi.Resource): class VersionV10(object): - def index(self, req): - return "test index 1.0" + def detail(self, req): + #TODO + return { + "version" : { + "id": "v1.0", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21-06:00", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.0/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + }, + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base" : "application/json", + "type" : "application/vnd.openstack.compute-v1.0+json" + } + ], + }, + } class VersionV11(object): - def index(self, req): - return "test index 1.1" + def detail(self, req): + return { + "version" : { + "id": "v1.1", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21-06:00", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.1/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + }, + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base" : "application/json", + "type" : "application/vnd.openstack.compute-v1.1+json" + } + ], + }, + } + class VersionsRequestDeserializer(wsgi.RequestDeserializer): def get_action_args(self, request_environment): """Parse dictionary created by routes library.""" - args = {} if request_environment['PATH_INFO'] == '/': args['action'] = 'index' @@ -129,6 +199,7 @@ class VersionsRequestDeserializer(wsgi.RequestDeserializer): return args + class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions): root = self._xml_doc.createElement('versions') @@ -158,6 +229,9 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) + def detail(self,data): + return "" + def multi(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) @@ -167,6 +241,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): class VersionsAtomSerializer(wsgi.XMLDictSerializer): def __init__(self, metadata=None, xmlns=None): + self.metadata = metadata or {} if not xmlns: self.xmlns = ATOM_XMLNS else: @@ -260,14 +335,9 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) - def multi(self, data): - self._xml_doc = minidom.Document() - node = self._xml_doc.createElementNS(self.xmlns, 'feed') - self._create_meta(node, data['versions']) - self._create_version_entries(node, data['versions']) - - return self.to_xml_string(node) - + def detail(self, data): + #TODO + pass def create_resource(version='1.0'): controller = { -- cgit From 2e142a02014940ebb1e775c26b60c576ad1e2bb3 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 25 Jul 2011 15:36:28 -0400 Subject: Added check to make sure there is a server entity in the create server request --- nova/api/openstack/create_instance_helper.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 7249f1261..55c07ed81 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -71,9 +71,12 @@ class CreateInstanceHelper(object): if not body: raise exc.HTTPUnprocessableEntity() - context = req.environ['nova.context'] + if not 'server' in body: + raise exc.HTTPUnprocessableEntity() - password = self.controller._get_server_admin_password(body['server']) + server_dict = body['server'] + context = req.environ['nova.context'] + password = self.controller._get_server_admin_password(server_dict) key_name = None key_data = None @@ -95,7 +98,7 @@ class CreateInstanceHelper(object): locals()) raise exc.HTTPBadRequest(explanation=msg) - personality = body['server'].get('personality') + personality = server_dict.get('personality') injected_files = [] if personality: @@ -107,18 +110,18 @@ class CreateInstanceHelper(object): msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) - if not 'name' in body['server']: + if not 'name' in server_dict: msg = _("Server name is not defined") raise exc.HTTPBadRequest(explanation=msg) - zone_blob = body['server'].get('blob') - name = body['server']['name'] + zone_blob = server_dict.get('blob') + name = server_dict['name'] self._validate_server_name(name) name = name.strip() - reservation_id = body['server'].get('reservation_id') - min_count = body['server'].get('min_count') - max_count = body['server'].get('max_count') + reservation_id = server_dict.get('reservation_id') + min_count = server_dict.get('min_count') + max_count = server_dict.get('max_count') # min_count and max_count are optional. If they exist, they come # in as strings. We want to default 'min_count' to 1, and default # 'max_count' to be 'min_count'. @@ -145,7 +148,7 @@ class CreateInstanceHelper(object): display_description=name, key_name=key_name, key_data=key_data, - metadata=body['server'].get('metadata', {}), + metadata=server_dict.get('metadata', {}), injected_files=injected_files, admin_password=password, zone_blob=zone_blob, -- cgit From 175e42fe2310a3aff69527d5623d92f3d0b19258 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 25 Jul 2011 15:56:23 -0400 Subject: adding testing to solidify handling of None in wsgi serialization --- nova/api/openstack/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 7842b821a..a908bdb8a 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -429,8 +429,8 @@ class ResponseSerializer(object): self.headers_serializer.serialize(response, data, action) def serialize_body(self, response, data, content_type, action): + response.headers['Content-Type'] = content_type if data is not None: - response.headers['Content-Type'] = content_type serializer = self.get_body_serializer(content_type) response.body = serializer.serialize(data, action) -- cgit From 71a103822b41df3d90a1e958baffda55a9cb8730 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 25 Jul 2011 16:25:19 -0400 Subject: xml version detail working with tests --- nova/api/openstack/versions.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 5655edcfc..00fc8d98f 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -209,16 +209,39 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return root - def _create_version_node(self, version): + def _create_media_types(self, media_types): + base = self._xml_doc.createElement('media-types') + for type in media_types: + node = self._xml_doc.createElement('media-type') + node.setAttribute('base', type['base']) + node.setAttribute('type', type['type']) + base.appendChild(node) + + return base + + def _create_version_node(self, version, create_ns=False): version_node = self._xml_doc.createElement('version') + if create_ns: + xmlns = "http://docs.openstack.org/common/api/%s" % version['id'] + xmlns_atom = "http://www.w3.org/2005/Atom" + version_node.setAttribute('xmlns', xmlns) + version_node.setAttribute('xmlns:atom', xmlns_atom) + version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) version_node.setAttribute('updated', version['updated']) + if 'media-types' in version: + media_types = self._create_media_types(version['media-types']) + version_node.appendChild(media_types) + for link in version['links']: link_node = self._xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) + if 'type' in link: + link_node.setAttribute('type', link['type']) + version_node.appendChild(link_node) return version_node @@ -230,7 +253,10 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) def detail(self,data): - return "" + self._xml_doc = minidom.Document() + node = self._create_version_node(data['version'], True) + + return self.to_xml_string(node) def multi(self, data): self._xml_doc = minidom.Document() -- cgit From b32b5571807c36f30d5541d0e284fd0e66023626 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 25 Jul 2011 17:00:19 -0400 Subject: adding xml serialization for /servers//ips and /servers//ips/ --- nova/api/openstack/ips.py | 56 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 2996b032d..a74fae487 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -16,6 +16,7 @@ # under the License. import time +from xml.dom import minidom from webob import exc @@ -100,17 +101,51 @@ class ControllerV11(Controller): return nova.api.openstack.views.addresses.ViewBuilderV11() +class IPXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self, xmlns=wsgi.XMLNS_V11): + super(IPXMLSerializer, self).__init__(xmlns=xmlns) + + def _ip_to_xml(self, xml_doc, ip_dict): + ip_node = xml_doc.createElement('ip') + ip_node.setAttribute('addr', ip_dict['addr']) + ip_node.setAttribute('version', str(ip_dict['version'])) + return ip_node + + def _network_to_xml(self, xml_doc, network_id, ip_dicts): + network_node = xml_doc.createElement('network') + network_node.setAttribute('id', network_id) + + for ip_dict in ip_dicts: + ip_node = self._ip_to_xml(xml_doc, ip_dict) + network_node.appendChild(ip_node) + + return network_node + + def networks_to_xml(self, xml_doc, networks_container): + addresses_node = xml_doc.createElement('addresses') + for (network_id, ip_dicts) in networks_container.items(): + network_node = self._network_to_xml(xml_doc, network_id, ip_dicts) + addresses_node.appendChild(network_node) + return addresses_node + + def show(self, network_container): + (network_id, ip_dicts) = network_container.items()[0] + xml_doc = minidom.Document() + node = self._network_to_xml(xml_doc, network_id, ip_dicts) + return self.to_xml_string(node, False) + + def index(self, addresses_container): + xml_doc = minidom.Document() + node = self.networks_to_xml(xml_doc, addresses_container['addresses']) + return self.to_xml_string(node, False) + + def create_resource(version): controller = { '1.0': ControllerV10, '1.1': ControllerV11, }[version]() - xmlns = { - '1.0': wsgi.XMLNS_V10, - '1.1': wsgi.XMLNS_V11, - }[version] - metadata = { 'list_collections': { 'public': {'item_name': 'ip', 'item_key': 'addr'}, @@ -118,10 +153,11 @@ def create_resource(version): }, } - body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=xmlns), - } - serializer = wsgi.ResponseSerializer(body_serializers) + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata=metadata, xmlns=wsgi.XMLNS_V11), + '1.1': IPXMLSerializer(), + }[version] + + serializer = wsgi.ResponseSerializer({'application/xml': xml_serializer}) return wsgi.Resource(controller, serializer=serializer) -- cgit From 7be2b2482fde20be8802cfe6a200590933a73d7e Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 25 Jul 2011 18:28:43 -0400 Subject: atom and xml_detail working, with tests --- nova/api/openstack/versions.py | 49 +++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 00fc8d98f..6fba4bbe0 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -116,7 +116,7 @@ class VersionV10(object): "version" : { "id": "v1.0", "status": "CURRENT", - "updated": "2011-01-21T11:33:21-06:00", + "updated": "2011-01-21T11:33:21Z", "links": [ { "rel": "self", @@ -155,7 +155,7 @@ class VersionV11(object): "version" : { "id": "v1.1", "status": "CURRENT", - "updated": "2011-01-21T11:33:21-06:00", + "updated": "2011-01-21T11:33:21Z", "links": [ { "rel": "self", @@ -298,8 +298,33 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): link_href = link_href.rstrip('/') return link_href.rsplit('/', 1)[0] + '/' - def _create_meta(self, root, versions): - title = self._create_text_elem('title', 'Available API Versions', + def _create_detail_meta(self, root, version): + title = self._create_text_elem('title', "About This Version", + type='text') + + updated = self._create_text_elem('updated', version['updated']) + + uri = version['links'][0]['href'] + id = self._create_text_elem('id', uri) + + link = self._xml_doc.createElement('link') + link.setAttribute('rel', 'self') + link.setAttribute('href', uri) + + author = self._xml_doc.createElement('author') + author_name = self._create_text_elem('name', 'Rackspace') + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/') + author.appendChild(author_name) + author.appendChild(author_uri) + + root.appendChild(title) + root.appendChild(updated) + root.appendChild(id) + root.appendChild(author) + root.appendChild(link) + + def _create_list_meta(self, root, versions): + title = self._create_text_elem('title', "Available API Versions", type='text') # Set this updated to the most recently updated version recent = self._get_most_recent_update(versions) @@ -307,6 +332,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): base_url = self._get_base_url(versions[0]['links'][0]['href']) id = self._create_text_elem('id', base_url) + link = self._xml_doc.createElement('link') link.setAttribute('rel', 'self') link.setAttribute('href', base_url) @@ -341,7 +367,10 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): link_node = self._xml_doc.createElement('link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) - entry.appendChild(link_node) + if 'type' in link: + link_node.setAttribute('type', link['type']) + + entry.appendChild(link_node) content = self._create_text_elem('content', 'Version %s %s (%s)' % @@ -356,14 +385,18 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def index(self, data): self._xml_doc = minidom.Document() node = self._xml_doc.createElementNS(self.xmlns, 'feed') - self._create_meta(node, data['versions']) + self._create_list_meta(node, data['versions']) self._create_version_entries(node, data['versions']) return self.to_xml_string(node) def detail(self, data): - #TODO - pass + self._xml_doc = minidom.Document() + node = self._xml_doc.createElementNS(self.xmlns, 'feed') + self._create_detail_meta(node, data['version']) + self._create_version_entries(node, [data['version']]) + + return self.to_xml_string(node) def create_resource(version='1.0'): controller = { -- cgit From eba09454a21ce49afa821ec63ed801883354ff7e Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 25 Jul 2011 20:34:41 -0400 Subject: initial stuff to get away from string comparisons for XML, and use ElementTree --- nova/api/openstack/versions.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 6fba4bbe0..c250dac8c 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -201,6 +201,13 @@ class VersionsRequestDeserializer(wsgi.RequestDeserializer): class VersionsXMLSerializer(wsgi.XMLDictSerializer): + #TODO(wwolf): this is temporary until we get rid of toprettyxml + # in the base class (XMLDictSerializer), which I plan to do in + # another branch + def to_xml_string(self, node, has_atom=False): + self._add_xmlns(node, has_atom) + return node.toxml(encoding='UTF-8') + def _versions_to_xml(self, versions): root = self._xml_doc.createElement('versions') -- cgit From 422d5329276f5c2252d7328d4112be7c696a274a Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 26 Jul 2011 09:57:39 -0400 Subject: Updated ServerXMLSerializer to utilize the IPXMLSerializer --- nova/api/openstack/servers.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4e6f0d7b5..11dafc272 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -28,6 +28,7 @@ from nova import log as logging from nova import utils from nova.api.openstack import common from nova.api.openstack import create_instance_helper as helper +from nova.api.openstack import ips import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images @@ -608,6 +609,7 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): def __init__(self): self.metadata_serializer = common.MetadataXMLSerializer() + self.addresses_serializer = ips.IPXMLSerializer() def _create_basic_entity_node(self, xml_doc, id, links, name): basic_node = xml_doc.createElement(name) @@ -621,23 +623,7 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata) def _create_addresses_node(self, xml_doc, addresses): - addresses_node = xml_doc.createElement('addresses') - for name, network_dict in addresses.items(): - network_node = self._create_network_node(xml_doc, - name, - network_dict) - addresses_node.appendChild(network_node) - return addresses_node - - def _create_network_node(self, xml_doc, network_name, network_dict): - network_node = xml_doc.createElement('network') - network_node.setAttribute('id', network_name) - for ip in network_dict: - ip_node = xml_doc.createElement('ip') - ip_node.setAttribute('version', str(ip['version'])) - ip_node.setAttribute('addr', ip['addr']) - network_node.appendChild(ip_node) - return network_node + return self.addresses_serializer.networks_to_xml(xml_doc, addresses) def _add_server_attributes(self, node, server): node.setAttribute('id', str(server['id'])) -- cgit From 696dc56b74a08d224beccdfd644536ec4217321d Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 11:15:44 -0400 Subject: updated atom tests --- nova/api/openstack/versions.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index c250dac8c..03b99f342 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -115,7 +115,7 @@ class VersionV10(object): return { "version" : { "id": "v1.0", - "status": "CURRENT", + "status": "DEPRECATED", "updated": "2011-01-21T11:33:21Z", "links": [ { @@ -415,7 +415,15 @@ def create_resource(version='1.0'): 'application/xml': VersionsXMLSerializer(), 'application/atom+xml': VersionsAtomSerializer(), } - serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(controller, serializer=serializer) + supported_content_types = ('application/json', + 'application/xml', + 'application/atom+xml') + deserializer = wsgi.RequestDeserializer( + supported_content_types=supported_content_types) + + + + return wsgi.Resource(controller, serializer=serializer, + deserializer=deserializer) -- cgit From bde063a98dad2ce75be1016b39a2c3f08759d4f6 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 11:30:58 -0400 Subject: got rid of string comparisons in serializer tests --- nova/api/openstack/versions.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 03b99f342..40c187607 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -205,6 +205,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): # in the base class (XMLDictSerializer), which I plan to do in # another branch def to_xml_string(self, node, has_atom=False): + print "TOXML" self._add_xmlns(node, has_atom) return node.toxml(encoding='UTF-8') @@ -273,6 +274,14 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): class VersionsAtomSerializer(wsgi.XMLDictSerializer): + #TODO(wwolf): this is temporary until we get rid of toprettyxml + # in the base class (XMLDictSerializer), which I plan to do in + # another branch + def to_xml_string(self, node, has_atom=False): + print "TOXML" + self._add_xmlns(node, has_atom) + return node.toxml(encoding='UTF-8') + def __init__(self, metadata=None, xmlns=None): self.metadata = metadata or {} if not xmlns: -- cgit From 26f980c955e357df3685bcccda005a3008f86afb Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 11:31:16 -0400 Subject: got rid of some prints --- nova/api/openstack/versions.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 40c187607..f389933b9 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -205,7 +205,6 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): # in the base class (XMLDictSerializer), which I plan to do in # another branch def to_xml_string(self, node, has_atom=False): - print "TOXML" self._add_xmlns(node, has_atom) return node.toxml(encoding='UTF-8') @@ -278,7 +277,6 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): # in the base class (XMLDictSerializer), which I plan to do in # another branch def to_xml_string(self, node, has_atom=False): - print "TOXML" self._add_xmlns(node, has_atom) return node.toxml(encoding='UTF-8') -- cgit From 1ad5f2eaf49904d8e14546d59699b1472a1a5bb2 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 12:57:06 -0400 Subject: xml deserialization works now --- nova/api/openstack/create_instance_helper.py | 79 ++++++---------------------- nova/api/openstack/wsgi.py | 9 ++-- 2 files changed, 19 insertions(+), 69 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 03272443a..b717b8ac0 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -303,79 +303,30 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): """Marshal the server attribute of a parsed request""" server = {} server_node = self.find_first_child_named(node, 'server') - for attr in ["name", "imageId", "flavorId"]: + + attributes = ["name", "imageId", "flavorId", "imageRef", + "flavorRef", "adminPass"] + for attr in attributes: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) - image = self._extract_image(server_node) - if image is not None: - server["image"] = image - flavor = self._extract_flavor(server_node) - if flavor is not None: - server["flavor"] = flavor - metadata_node = self.find_first_child_named(server_node, "metadata") - metadata = self.extract_metadata(metadata_node) - if metadata is not None: - server["metadata"] = metadata - personality = self._extract_personality(server_node) - if personality is not None: - server["personality"] = personality - return server - - def _extract_image(self, server_node): - """Retrieve an image entity from the server node""" - image_node = self.find_first_child_named(server_node, "image") - if image_node is None: - return None - - image = {} - image_id = image_node.getAttribute('id') - if image_id is not None: - image['id'] = image_id - - image['links'] = self._extract_links_from_node(image_node) - - return image - def _extract_flavor(self, server_node): - """Retrieve a flavor entity from the server node""" - flavor_node = self.find_first_child_named(server_node, "flavor") - if flavor_node is None: - return None - - flavor = {} - flavor_id = flavor_node.getAttribute('id') - if flavor_id: - flavor['id'] = flavor_id - - flavor['links'] = self._extract_links_from_node(flavor_node) - - return flavor - - def _extract_links_from_node(self, parent_node): - """Retrieve link entities from a links container provided node""" - links = [] + metadata_node = self.find_first_child_named(server_node, "metadata") + server["metadata"] = self.extract_metadata(metadata_node) - for link_node in self.find_children_named(parent_node, 'atom:link'): - link = { - 'rel': link_node.getAttribute('rel'), - 'href': link_node.getAttribute('href'), - } - if link['rel'] is not None and link['href'] is not None: - links.append(link) + server["personality"] = self._extract_personality(server_node) - return links + return server def _extract_personality(self, server_node): """Marshal the personality attribute of a parsed request""" personality_node = \ self.find_first_child_named(server_node, "personality") - if personality_node is None: - return None personality = [] - for file_node in self.find_children_named(personality_node, "file"): - item = {} - if file_node.hasAttribute("path"): - item["path"] = file_node.getAttribute("path") - item["contents"] = self.extract_text(file_node) - personality.append(item) + if personality_node is not None: + for file_node in self.find_children_named(personality_node, "file"): + item = {} + if file_node.hasAttribute("path"): + item["path"] = file_node.getAttribute("path") + item["contents"] = self.extract_text(file_node) + personality.append(item) return personality diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index a28443d12..53dab22e8 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -165,12 +165,11 @@ class MetadataXMLDeserializer(XMLDeserializer): def extract_metadata(self, metadata_node): """Marshal the metadata attribute of a parsed request""" - if metadata_node is None: - return None metadata = {} - for meta_node in self.find_children_named(metadata_node, "meta"): - key = meta_node.getAttribute("key") - metadata[key] = self.extract_text(meta_node) + if metadata_node is not None: + for meta_node in self.find_children_named(metadata_node, "meta"): + key = meta_node.getAttribute("key") + metadata[key] = self.extract_text(meta_node) return metadata -- cgit From 3db1c53486fdb669ac2bab303335548d7a7c617d Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 13:28:11 -0400 Subject: updating imageRef and flavorRef parsing --- nova/api/openstack/servers.py | 42 +++++------------------------------------- 1 file changed, 5 insertions(+), 37 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e1f845f36..06112eee3 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -493,51 +493,19 @@ class ControllerV11(Controller): def _image_ref_from_req_data(self, data): try: - image = data['server']['image'] + return data['server']['imageRef'] except (TypeError, KeyError): - msg = _("Missing image entity") + msg = _("Missing imageRef attribute") raise exc.HTTPBadRequest(explanation=msg) - try: - links = image.get('links', []) - except AttributeError: - msg = _("Malformed image entity") - raise exc.HTTPBadRequest(explanation=msg) - - image_ref = self._href_from_bookmark_links(links) - - if image_ref is None: - try: - return image['id'] - except KeyError: - msg = _("Missing id attribute on image entity") - raise exc.HTTPBadRequest(explanation=msg) - else: - return image_ref - def _flavor_id_from_req_data(self, data): try: - flavor = data['server']['flavor'] + flavor_ref = data['server']['flavorRef'] except (TypeError, KeyError): - msg = _("Missing flavor entity") + msg = _("Missing flavorRef attribute") raise exc.HTTPBadRequest(explanation=msg) - try: - links = flavor.get('links', []) - except AttributeError: - msg = _("Malformed flavor entity") - raise exc.HTTPBadRequest(explanation=msg) - - flavor_ref = self._href_from_bookmark_links(links) - - if flavor_ref is None: - try: - return flavor['id'] - except (KeyError, AttributeError): - msg = _("Missing id attribute in flavor entity") - raise exc.HTTPBadRequest(explanation=msg) - else: - return common.get_id_from_href(flavor_ref) + return common.get_id_from_href(flavor_ref) def _build_view(self, req, instance, is_detail=False): base_url = req.application_url -- cgit From 94866fef798a6b72061720cb654442cd194b9f5f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 13:47:37 -0400 Subject: reverting tests to use imageRef, flavorRef --- nova/api/openstack/create_instance_helper.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index b717b8ac0..1342397c4 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -319,11 +319,10 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): def _extract_personality(self, server_node): """Marshal the personality attribute of a parsed request""" - personality_node = \ - self.find_first_child_named(server_node, "personality") + node = self.find_first_child_named(server_node, "personality") personality = [] - if personality_node is not None: - for file_node in self.find_children_named(personality_node, "file"): + if node is not None: + for file_node in self.find_children_named(node, "file"): item = {} if file_node.hasAttribute("path"): item["path"] = file_node.getAttribute("path") -- cgit From 7cd146e7e658c51cc94664d8da8d2bc15b0141fc Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 26 Jul 2011 13:49:04 -0400 Subject: fixed minor issues --- nova/api/openstack/servers.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 11dafc272..194ec82d5 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -630,8 +630,8 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): node.setAttribute('uuid', str(server['uuid'])) node.setAttribute('hostId', str(server['hostId'])) node.setAttribute('name', server['name']) - node.setAttribute('created', server['created']) - node.setAttribute('updated', server['updated']) + node.setAttribute('created', str(server['created'])) + node.setAttribute('updated', str(server['updated'])) node.setAttribute('status', server['status']) if 'progress' in server: node.setAttribute('progress', str(server['progress'])) @@ -655,17 +655,19 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): for link_node in link_nodes: server_node.appendChild(link_node) - image_node = self._create_basic_entity_node(xml_doc, + if 'image' in server: + image_node = self._create_basic_entity_node(xml_doc, server['image']['id'], server['image']['links'], 'image') - server_node.appendChild(image_node) + server_node.appendChild(image_node) - flavor_node = self._create_basic_entity_node(xml_doc, + if 'flavor' in server: + flavor_node = self._create_basic_entity_node(xml_doc, server['flavor']['id'], server['flavor']['links'], 'flavor') - server_node.appendChild(flavor_node) + server_node.appendChild(flavor_node) metadata = server.get('metadata', {}).items() if len(metadata) > 0: -- cgit From 24e1f23dbaf9ffd3f42fe05c24b980a6a0f09499 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 14:06:02 -0400 Subject: removing extra function --- nova/api/openstack/servers.py | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 06112eee3..618778ea3 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -480,17 +480,6 @@ class ControllerV11(Controller): except exception.NotFound: raise exc.HTTPNotFound() - def _href_from_bookmark_links(self, links): - for link in links: - try: - if link.get('rel') == 'bookmark': - href = link.get('href') - if href is not None: - return href - except AttributeError: - msg = _("Malformed link entity") - raise exc.HTTPBadRequest(explanation=msg) - def _image_ref_from_req_data(self, data): try: return data['server']['imageRef'] -- cgit From 241a926ed682cb6154ff8f37c4940e7b5885b6fe Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 16:13:09 -0400 Subject: moved v1.1 image creation from /images to /servers//action --- nova/api/openstack/images.py | 37 ++++++++--------- nova/api/openstack/servers.py | 93 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 24 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 30e4fd389..517a51662 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -98,6 +98,20 @@ class Controller(object): self._image_service.delete(context, id) return webob.exc.HTTPNoContent() + def get_builder(self, request): + """Indicates that you must use a Controller subclass.""" + raise NotImplementedError() + + def _server_id_from_req(self, req, data): + raise NotImplementedError() + + def _get_extra_properties(self, req, data): + return {} + + +class ControllerV10(Controller): + """Version 1.0 specific controller logic.""" + def create(self, req, body): """Snapshot or backup a server instance and save the image. @@ -157,20 +171,6 @@ class Controller(object): return dict(image=self.get_builder(req).build(image, detail=True)) - def get_builder(self, request): - """Indicates that you must use a Controller subclass.""" - raise NotImplementedError() - - def _server_id_from_req(self, req, data): - raise NotImplementedError() - - def _get_extra_properties(self, req, data): - return {} - - -class ControllerV10(Controller): - """Version 1.0 specific controller logic.""" - def get_builder(self, request): """Property to get the ViewBuilder class we need to use.""" base_url = request.application_url @@ -278,6 +278,9 @@ class ControllerV11(Controller): server_ref) return {'instance_ref': server_ref} + def create(self, *args, **kwargs): + raise webob.exc.HTTPMethodNotAllowed() + class ImageXMLSerializer(wsgi.XMLDictSerializer): @@ -369,12 +372,6 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): image_dict['image']) return self.to_xml_string(node, True) - def create(self, image_dict): - xml_doc = minidom.Document() - node = self._image_to_xml_detailed(xml_doc, - image_dict['image']) - return self.to_xml_string(node, True) - def create_resource(version='1.0'): controller = { diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index d7cabb067..5371e62c9 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -14,6 +14,7 @@ # under the License. import base64 +import os import traceback from webob import exc @@ -155,20 +156,26 @@ class Controller(object): """Multi-purpose method used to reboot, rebuild, or resize a server""" - actions = { + self.actions = { 'changePassword': self._action_change_password, 'reboot': self._action_reboot, 'resize': self._action_resize, 'confirmResize': self._action_confirm_resize, 'revertResize': self._action_revert_resize, 'rebuild': self._action_rebuild, - 'migrate': self._action_migrate} + 'migrate': self._action_migrate, + 'createImage': self._action_create_image, + } - for key in actions.keys(): + + for key in self.actions.keys(): if key in body: - return actions[key](body, req, id) + return self.actions[key](body, req, id) raise exc.HTTPNotImplemented() + def _action_create_image(self, input_dict, req, id): + return exc.HTTPNotImplemented() + def _action_change_password(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -585,6 +592,80 @@ class ControllerV11(Controller): return webob.Response(status_int=202) + def _action_create_image(self, input_dict, req, instance_id): + """Snapshot or backup a server instance and save the image. + + Images now have an `image_type` associated with them, which can be + 'snapshot' or the backup type, like 'daily' or 'weekly'. + + If the image_type is backup-like, then the rotation factor can be + included and that will cause the oldest backups that exceed the + rotation factor to be deleted. + + """ + entity = input_dict.get('createImage', {}) + + def get_param(param): + try: + return entity[param] + except KeyError: + msg = _("Missing required param: %s") % param + raise webob.exc.HTTPBadRequest(explanation=msg) + + context = req.environ['nova.context'] + + image_name = get_param("name") + image_type = entity.get("image_type", "snapshot") + + # preserve link to server in image properties + server_ref = os.path.join(req.application_url, + 'servers', + str(instance_id)) + props = {'instance_ref': server_ref} + + metadata = entity.get('metadata', {}) + try: + props.update(metadata) + except ValueError: + msg = _("Invalid metadata") + raise webob.exc.HTTPBadRequest(explanation=msg) + + if image_type == "snapshot": + image = self.compute_api.snapshot(context, + instance_id, + image_name, + extra_properties=props) + + elif image_type == "backup": + # NOTE(sirp): Unlike snapshot, backup is not a customer facing + # API call; rather, it's used by the internal backup scheduler + if not FLAGS.allow_admin_api: + msg = _("Admin API Required") + raise webob.exc.HTTPBadRequest(explanation=msg) + + backup_type = get_param("backup_type") + rotation = int(get_param("rotation")) + + image = self.compute_api.backup(context, + instance_id, + image_name, + backup_type, + rotation, + extra_properties=props) + else: + msg = _("Invalid image_type '%s'") % image_type + raise webob.exc.HTTPBadRequest(explanation=msg) + + + # build location of newly-created image entity + image_ref = os.path.join(req.application_url, + 'images', + str(image['id'])) + + resp = webob.Response(status_int=202) + resp.headers['Location'] = image_ref + return resp + def get_default_xmlns(self, req): return common.XML_NS_V11 @@ -593,6 +674,10 @@ class ControllerV11(Controller): return self.helper._get_server_admin_password_new_style(server) + + + + class HeadersSerializer(wsgi.ResponseHeadersSerializer): def delete(self, response, data): -- cgit From 2e652f4cc72976ecc471a6c6f3b48afb3eb5a420 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 26 Jul 2011 16:42:16 -0400 Subject: Updated test stubs to contain the correct data Updated created and updated in responses to use correct time format --- nova/api/openstack/views/servers.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index be25e1e40..c90801724 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import hashlib import os @@ -149,8 +150,10 @@ class ViewBuilderV11(ViewBuilder): def _build_detail(self, inst): response = super(ViewBuilderV11, self)._build_detail(inst) - response['server']['created'] = inst['created_at'] - response['server']['updated'] = inst['updated_at'] + response['server']['created'] = \ + self._convert_timeformat(inst['created_at']) + response['server']['updated'] = \ + self._convert_timeformat(inst['updated_at']) if 'status' in response['server']: if response['server']['status'] == "ACTIVE": response['server']['progress'] = 100 @@ -221,3 +224,11 @@ class ViewBuilderV11(ViewBuilder): """Create an url that refers to a specific flavor id.""" return os.path.join(common.remove_version_from_href(self.base_url), "servers", str(server_id)) + + def _convert_timeformat(self, time): + """Converts the given time into the common time format + + :param time: should be a datetime + + """ + return time.strftime(utils.TIME_FORMAT) -- cgit From e4e9d7550c2eb29c5d8fed3af0b9112976d262de Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 26 Jul 2011 20:44:29 +0000 Subject: Update security gropu rules to properly support new format and boto 2.0 --- nova/api/ec2/cloud.py | 53 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 19 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 10720a804..4b8c4cf80 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -539,15 +539,20 @@ class CloudController(object): return rules if 'ip_ranges' in kwargs: rules = self._cidr_args_split(kwargs) + else: + rules = [kwargs] finalset = [] for rule in rules: if 'groups' in rule: groups_values = self._groups_args_split(rule) for groups_value in groups_values: - finalset.append(groups_value) + final = self._rule_dict_last_step(context, **groups_value) + finalset.append(final) else: if rule: - finalset.append(rule) + final = self._rule_dict_last_step(context, **rule) + finalset.append(final) + LOG.warn(finalset) return finalset def _cidr_args_split(self, kwargs): @@ -590,6 +595,9 @@ class CloudController(object): db.security_group_get_by_name(context.elevated(), source_project_id, source_security_group_name) + notfound = exception.SecurityGroupNotFound + if not source_security_group: + raise notfound(security_group_id=source_security_group_name) values['group_id'] = source_security_group['id'] elif cidr_ip: # If this fails, it throws an exception. This is what we want. @@ -628,7 +636,7 @@ class CloudController(object): for rule in security_group.rules: if 'group_id' in values: if rule['group_id'] == values['group_id']: - return True + return rule['id'] else: is_duplicate = True for key in ('cidr', 'from_port', 'to_port', 'protocol'): @@ -636,7 +644,7 @@ class CloudController(object): is_duplicate = False break if is_duplicate: - return True + return rule['id'] return False def revoke_security_group_ingress(self, context, group_name=None, @@ -659,23 +667,30 @@ class CloudController(object): msg = "Revoke security group ingress %s" LOG.audit(_(msg), security_group['name'], context=context) + prevalues = [] + try: + prevalues = kwargs['ip_permissions'] + except KeyError: + prevalues.append(kwargs) + postvalues = [] + for values in prevalues: + rulesvalues = self._rule_args_to_dict(context, values) + if not rulesvalues: + err = "%s Not enough parameters to build a valid rule" + raise exception.ApiError(_(err % rulesvalues)) - criteria = self._rule_args_to_dict(context, kwargs)[0] - if criteria is None: - raise exception.ApiError(_("Not enough parameters to build a " - "valid rule.")) - - for rule in security_group.rules: - match = True - for (k, v) in criteria.iteritems(): - if getattr(rule, k, False) != v: - match = False - if match: - db.security_group_rule_destroy(context, rule['id']) - self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) + rule_id = None + for values_for_rule in rulesvalues: + values_for_rule['parent_group_id'] = security_group.id + rule_id = self._security_group_rule_exists(security_group, + values_for_rule) + if rule_id: + db.security_group_rule_destroy(context, rule_id) + self.compute_api.trigger_security_group_rules_refresh(context, + security_group_id=security_group['id']) + if rule_id: return True - raise exception.ApiError(_("No rule for the specified parameters.")) + raise exception.ApiError(_("No rule for the specified parameters.")) # TODO(soren): This has only been tested with Boto as the client. # Unfortunately, it seems Boto is using an old API -- cgit From 5bd11c44f791cfe4c371b33cd1b1253013a8f836 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 26 Jul 2011 20:49:46 +0000 Subject: remove some logging, remove extra if --- nova/api/ec2/cloud.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4b8c4cf80..4045dfc61 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -549,10 +549,8 @@ class CloudController(object): final = self._rule_dict_last_step(context, **groups_value) finalset.append(final) else: - if rule: - final = self._rule_dict_last_step(context, **rule) - finalset.append(final) - LOG.warn(finalset) + final = self._rule_dict_last_step(context, **rule) + finalset.append(final) return finalset def _cidr_args_split(self, kwargs): -- cgit From c8b1a357c9bd5fe4bc54e6472f9667123d91c02a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 16:53:00 -0400 Subject: adding xml deserialization for createImage action --- nova/api/openstack/create_instance_helper.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index f8317565e..eb556ae8b 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -293,6 +293,30 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): and personality attributes """ + def action(self, string): + dom = minidom.parseString(string) + action_node = dom.childNodes[0] + action_name = action_node.tagName + + action_deserializer = { + 'createImage': self._action_create_image, + }.get(action_name, self.default) + + action_data = action_deserializer(action_node) + + return {'body': {action_name: action_data}} + + def _action_create_image(self, node): + data = {} + attributes = ['name', 'image_type', 'backup_type', 'rotation'] + for attribute in attributes: + value = node.getAttribute(attribute) + if value: + data[attribute] = value + metadata_node = self.find_first_child_named(node, 'metadata') + data['metadata'] = self.extract_metadata(metadata_node) + return data + def create(self, string): """Deserialize an xml-formatted server create request""" dom = minidom.parseString(string) -- cgit From d4b2a2b3d552103414e4052773ac97939c66fa53 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 26 Jul 2011 20:58:02 +0000 Subject: pep8 and simplify rule refresh logic --- nova/api/ec2/cloud.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4045dfc61..0294c09c5 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -670,25 +670,26 @@ class CloudController(object): prevalues = kwargs['ip_permissions'] except KeyError: prevalues.append(kwargs) - postvalues = [] + rule_id = None for values in prevalues: rulesvalues = self._rule_args_to_dict(context, values) if not rulesvalues: err = "%s Not enough parameters to build a valid rule" raise exception.ApiError(_(err % rulesvalues)) - rule_id = None for values_for_rule in rulesvalues: values_for_rule['parent_group_id'] = security_group.id rule_id = self._security_group_rule_exists(security_group, values_for_rule) if rule_id: db.security_group_rule_destroy(context, rule_id) - self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) - if rule_id: - return True - raise exception.ApiError(_("No rule for the specified parameters.")) + if rule_id: + # NOTE(vish): we removed a rule, so refresh + self.compute_api.trigger_security_group_rules_refresh( + context, + security_group_id=security_group['id']) + return True + raise exception.ApiError(_("No rule for the specified parameters.")) # TODO(soren): This has only been tested with Boto as the client. # Unfortunately, it seems Boto is using an old API @@ -734,15 +735,17 @@ class CloudController(object): postvalues.append(values_for_rule) for values_for_rule in postvalues: - security_group_rule = db.security_group_rule_create(context, - values_for_rule) + security_group_rule = db.security_group_rule_create( + context, + values_for_rule) - self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) + if postvalues: + self.compute_api.trigger_security_group_rules_refresh( + context, + security_group_id=security_group['id']) + return True - group = db.security_group_get_by_name(context, context.project_id, - security_group['name']) - return True + raise exception.ApiError(_("No rule for the specified parameters.")) def _get_source_project_id(self, context, source_security_group_owner_id): if source_security_group_owner_id: -- cgit From 84909f4a7733dde453afcc5cc540854ac1bc458c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 17:00:10 -0400 Subject: pep8 --- nova/api/openstack/servers.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7a900cb54..0cc81009b 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -153,8 +153,7 @@ class Controller(object): @scheduler_api.redirect_handler def action(self, req, id, body): - """Multi-purpose method used to reboot, rebuild, or - resize a server""" + """Multi-purpose method used to take actions on a server""" self.actions = { 'changePassword': self._action_change_password, @@ -167,7 +166,6 @@ class Controller(object): 'createImage': self._action_create_image, } - for key in self.actions.keys(): if key in body: return self.actions[key](body, req, id) @@ -665,7 +663,6 @@ class ControllerV11(Controller): msg = _("Invalid image_type '%s'") % image_type raise webob.exc.HTTPBadRequest(explanation=msg) - # build location of newly-created image entity image_ref = os.path.join(req.application_url, 'images', @@ -683,10 +680,6 @@ class ControllerV11(Controller): return self.helper._get_server_admin_password_new_style(server) - - - - class HeadersSerializer(wsgi.ResponseHeadersSerializer): def delete(self, response, data): -- cgit From e14754bbdbacaf6943c4061e3488f2580acd26ad Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 17:51:46 -0400 Subject: initial working 300 multiple choice stuff --- nova/api/openstack/__init__.py | 2 +- nova/api/openstack/versions.py | 191 ++++++++++++++++++++--------------- nova/api/openstack/views/versions.py | 8 ++ 3 files changed, 117 insertions(+), 84 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index c10937bd6..0dfad0ef6 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -118,7 +118,7 @@ class APIRouter(base_wsgi.Router): mapper.connect("versions", "/", controller=versions.create_resource(version), - action="detail") + action='show') mapper.resource("console", "consoles", controller=consoles.create_resource(), diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index f389933b9..4b567957d 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -25,6 +25,80 @@ from nova.api.openstack import wsgi ATOM_XMLNS = "http://www.w3.org/2005/Atom" +VERSIONS = { + "v1.0": { + "version" : { + "id": "v1.0", + "status": "DEPRECATED", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.0/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + }, + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base" : "application/json", + "type" : "application/vnd.openstack.compute-v1.0+json" + } + ], + }, + }, + "v1.1": { + "version" : { + "id": "v1.1", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "http://servers.api.openstack.org/v1.1/" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + }, + ], + "media-types": [ + { + "base" : "application/xml", + "type" : "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base" : "application/json", + "type" : "application/vnd.openstack.compute-v1.1+json" + } + ], + }, + }, +} + + class Versions(wsgi.Resource): @@ -43,11 +117,15 @@ class Versions(wsgi.Resource): } } + headers_serializer = VersionsHeadersSerializer() + body_serializers = { 'application/atom+xml': VersionsAtomSerializer(metadata=metadata), 'application/xml': VersionsXMLSerializer(metadata=metadata), } - serializer = wsgi.ResponseSerializer(body_serializers) + serializer = wsgi.ResponseSerializer( + body_serializers=body_serializers, + headers_serializer=headers_serializer) supported_content_types = ('application/json', 'application/xml', @@ -93,100 +171,41 @@ class Versions(wsgi.Resource): { "id": "v1.1", "status": "CURRENT", - #TODO(wwolf) get correct value for these - "updated": "2011-07-18T11:30:00Z", + "links": [ + { + "rel": "self", + } + ], + "media-types": VERSIONS['v1.1']['version']['media-types'], }, { "id": "v1.0", "status": "DEPRECATED", - #TODO(wwolf) get correct value for these - "updated": "2010-10-09T11:30:00Z", + "links": [ + { + "rel": "self", + } + ], + "media-types": VERSIONS['v1.0']['version']['media-types'], }, ] builder = nova.api.openstack.views.versions.get_view_builder(request) - versions = [builder.build(version) for version in version_objs] - return dict(versions=versions) + choices = [ + builder.build_choices(version, request) + for version in version_objs] + + return dict(choices=choices) class VersionV10(object): - def detail(self, req): - #TODO - return { - "version" : { - "id": "v1.0", - "status": "DEPRECATED", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "http://servers.api.openstack.org/v1.0/" - }, - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" - }, - ], - "media-types": [ - { - "base" : "application/xml", - "type" : "application/vnd.openstack.compute-v1.0+xml" - }, - { - "base" : "application/json", - "type" : "application/vnd.openstack.compute-v1.0+json" - } - ], - }, - } + def show(self, req): + return VERSIONS['v1.0'] class VersionV11(object): - def detail(self, req): - return { - "version" : { - "id": "v1.1", - "status": "CURRENT", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "http://servers.api.openstack.org/v1.1/" - }, - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" - }, - ], - "media-types": [ - { - "base" : "application/xml", - "type" : "application/vnd.openstack.compute-v1.1+xml" - }, - { - "base" : "application/json", - "type" : "application/vnd.openstack.compute-v1.1+json" - } - ], - }, - } - + def show(self, req): + return VERSIONS['v1.1'] class VersionsRequestDeserializer(wsgi.RequestDeserializer): def get_action_args(self, request_environment): @@ -259,7 +278,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) - def detail(self,data): + def show(self,data): self._xml_doc = minidom.Document() node = self._create_version_node(data['version'], True) @@ -404,7 +423,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) - def detail(self, data): + def show(self, data): self._xml_doc = minidom.Document() node = self._xml_doc.createElementNS(self.xmlns, 'feed') self._create_detail_meta(node, data['version']) @@ -412,6 +431,12 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) + +class VersionsHeadersSerializer(wsgi.ResponseHeadersSerializer): + def multi(self, response, data): + response.status_int = 300 + + def create_resource(version='1.0'): controller = { '1.0': VersionV10, diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 9fa8f49dc..97e35c983 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -31,6 +31,11 @@ class ViewBuilder(object): """ self.base_url = base_url + def build_choices(self, version_data, request): + version_data['links'][0]['href'] = self._build_versioned_link(request, + version_data['id']) + return version_data + def build(self, version_data): """Generic method used to generate a version entity.""" version = { @@ -42,6 +47,9 @@ class ViewBuilder(object): return version + def _build_versioned_link(self, req, version): + return '%s://%s/%s%s' % (req.scheme, req.host, version, req.path) + def _build_links(self, version_data): """Generate a container of links that refer to the provided version.""" href = self.generate_href(version_data["id"]) -- cgit From 6dbd7583f4f1ca4be59e163c4c568423a91cd29e Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 26 Jul 2011 18:10:36 -0400 Subject: pep8 fixes --- nova/api/openstack/__init__.py | 4 ++-- nova/api/openstack/versions.py | 31 ++++++++++++++----------------- 2 files changed, 16 insertions(+), 19 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 0dfad0ef6..96a2f20e0 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -116,8 +116,8 @@ class APIRouter(base_wsgi.Router): 'select': 'POST', 'boot': 'POST'}) - mapper.connect("versions", "/", - controller=versions.create_resource(version), + mapper.connect("versions", "/", + controller=versions.create_resource(version), action='show') mapper.resource("console", "consoles", diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 4b567957d..58c767d5c 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -25,12 +25,12 @@ from nova.api.openstack import wsgi ATOM_XMLNS = "http://www.w3.org/2005/Atom" -VERSIONS = { +VERSIONS = { "v1.0": { - "version" : { + "version" : { "id": "v1.0", "status": "DEPRECATED", - "updated": "2011-01-21T11:33:21Z", + "updated": "2011-01-21T11:33:21Z", "links": [ { "rel": "self", @@ -41,7 +41,7 @@ VERSIONS = { "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" "servers/api/v1.0/cs-devguide-20110125.pdf" - }, + }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", @@ -62,7 +62,7 @@ VERSIONS = { }, }, "v1.1": { - "version" : { + "version" : { "id": "v1.1", "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", @@ -76,7 +76,7 @@ VERSIONS = { "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" "servers/api/v1.1/cs-devguide-20110125.pdf" - }, + }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", @@ -99,8 +99,6 @@ VERSIONS = { } - - class Versions(wsgi.Resource): @classmethod def factory(cls, global_config, **local_config): @@ -192,7 +190,7 @@ class Versions(wsgi.Resource): builder = nova.api.openstack.views.versions.get_view_builder(request) choices = [ - builder.build_choices(version, request) + builder.build_choices(version, request) for version in version_objs] return dict(choices=choices) @@ -207,6 +205,7 @@ class VersionV11(object): def show(self, req): return VERSIONS['v1.1'] + class VersionsRequestDeserializer(wsgi.RequestDeserializer): def get_action_args(self, request_environment): """Parse dictionary created by routes library.""" @@ -220,8 +219,8 @@ class VersionsRequestDeserializer(wsgi.RequestDeserializer): class VersionsXMLSerializer(wsgi.XMLDictSerializer): - #TODO(wwolf): this is temporary until we get rid of toprettyxml - # in the base class (XMLDictSerializer), which I plan to do in + #TODO(wwolf): this is temporary until we get rid of toprettyxml + # in the base class (XMLDictSerializer), which I plan to do in # another branch def to_xml_string(self, node, has_atom=False): self._add_xmlns(node, has_atom) @@ -244,7 +243,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): base.appendChild(node) return base - + def _create_version_node(self, version, create_ns=False): version_node = self._xml_doc.createElement('version') if create_ns: @@ -278,7 +277,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return self.to_xml_string(node) - def show(self,data): + def show(self, data): self._xml_doc = minidom.Document() node = self._create_version_node(data['version'], True) @@ -292,8 +291,8 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): class VersionsAtomSerializer(wsgi.XMLDictSerializer): - #TODO(wwolf): this is temporary until we get rid of toprettyxml - # in the base class (XMLDictSerializer), which I plan to do in + #TODO(wwolf): this is temporary until we get rid of toprettyxml + # in the base class (XMLDictSerializer), which I plan to do in # another branch def to_xml_string(self, node, has_atom=False): self._add_xmlns(node, has_atom) @@ -455,7 +454,5 @@ def create_resource(version='1.0'): deserializer = wsgi.RequestDeserializer( supported_content_types=supported_content_types) - - return wsgi.Resource(controller, serializer=serializer, deserializer=deserializer) -- cgit From c95ee1625a6a88afdb77d305077d1ee7eeaae854 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 26 Jul 2011 16:20:31 -0700 Subject: fix for lp816713: In instance creation, when nova-api is passed imageRefs generated by itself, strip the url down to an id so that default glance connection params are used --- nova/api/openstack/create_instance_helper.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index f8317565e..0e6c0a87c 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -87,6 +87,10 @@ class CreateInstanceHelper(object): key_data = key_pair['public_key'] image_href = self.controller._image_ref_from_req_data(body) + # If the image href was generated by nova api, strip image_href + # down to an id and use the default glance connection params + if image_href.startswith(req.application_url): + image_href = image_href.split('/').pop() try: image_service, image_id = nova.image.get_image_service(image_href) kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( -- cgit From 6b33d0dfbfea7ee66a47947973133573070303cd Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 26 Jul 2011 23:03:16 -0700 Subject: code was checking for key in sqlalchemy instance but if floating_ip is a non-sqlalchemy dict instance instead, value=None will cause NoneType exception. --- nova/api/openstack/contrib/floating_ips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index b4a211857..a02f34b8c 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -27,7 +27,7 @@ from nova.api.openstack import extensions def _translate_floating_ip_view(floating_ip): result = {'id': floating_ip['id'], 'ip': floating_ip['address']} - if 'fixed_ip' in floating_ip: + if 'fixed_ip' in floating_ip and floating_ip['fixed_ip']: result['fixed_ip'] = floating_ip['fixed_ip']['address'] else: result['fixed_ip'] = None -- cgit From 74e3218d2b6045457019c4de518ca4a869e37807 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 09:11:41 -0400 Subject: multi choice XML responses with tests --- nova/api/openstack/versions.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 58c767d5c..9909d8d0d 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -25,6 +25,7 @@ from nova.api.openstack import wsgi ATOM_XMLNS = "http://www.w3.org/2005/Atom" +OS_XMLNS_BASE = "http://docs.openstack.org/common/api/" VERSIONS = { "v1.0": { "version" : { @@ -226,8 +227,10 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): self._add_xmlns(node, has_atom) return node.toxml(encoding='UTF-8') - def _versions_to_xml(self, versions): - root = self._xml_doc.createElement('versions') + def _versions_to_xml(self, versions, name="versions", xmlns=None): + root = self._xml_doc.createElement(name) + root.setAttribute("xmlns", "%sv1.0" % OS_XMLNS_BASE) + root.setAttribute("xmlns:atom", ATOM_XMLNS) for version in versions: root.appendChild(self._create_version_node(version)) @@ -247,14 +250,15 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _create_version_node(self, version, create_ns=False): version_node = self._xml_doc.createElement('version') if create_ns: - xmlns = "http://docs.openstack.org/common/api/%s" % version['id'] + xmlns = "%s%s" % (OS_XMLNS_BASE, version['id']) xmlns_atom = "http://www.w3.org/2005/Atom" version_node.setAttribute('xmlns', xmlns) version_node.setAttribute('xmlns:atom', xmlns_atom) version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) - version_node.setAttribute('updated', version['updated']) + if 'updated' in version: + version_node.setAttribute('updated', version['updated']) if 'media-types' in version: media_types = self._create_media_types(version['media-types']) @@ -285,7 +289,8 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def multi(self, data): self._xml_doc = minidom.Document() - node = self._versions_to_xml(data['versions']) + node = self._versions_to_xml(data['choices'], 'choices', + xmlns="%sv1.0" % OS_XMLNS_BASE) return self.to_xml_string(node) -- cgit From 0760948609ac89a43a590b36e79d691a6c79b4c3 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 11:32:18 -0400 Subject: updating servers metadata resource --- nova/api/openstack/__init__.py | 11 +++- nova/api/openstack/common.py | 4 +- nova/api/openstack/server_metadata.py | 113 ++++++++++++++++++++++------------ 3 files changed, 87 insertions(+), 41 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 1d14474a8..6585f1751 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -164,7 +164,9 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper, '1.1') + image_metadata_controller = image_metadata.create_resource() + mapper.resource("image_meta", "metadata", controller=image_metadata_controller, parent_resource=dict(member_name='image', @@ -175,7 +177,14 @@ class APIRouterV11(APIRouter): action='update_all', conditions={"method": ['PUT']}) + server_metadata_controller = server_metadata.create_resource() + mapper.resource("server_meta", "metadata", - controller=server_metadata.create_resource(), + controller=server_metadata_controller, parent_resource=dict(member_name='server', collection_name='servers')) + + mapper.connect("metadata", "/servers/{server_id}/metadata", + controller=server_metadata_controller, + action='update_all', + conditions={"method": ['PUT']}) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index efa4ab385..24c82035a 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -247,7 +247,7 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer): container_node = self.meta_list_to_xml(xml_doc, items) xml_doc.appendChild(container_node) self._add_xmlns(container_node) - return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') + return xml_doc.toxml('UTF-8') def index(self, metadata_dict): return self._meta_list_to_xml_string(metadata_dict) @@ -264,7 +264,7 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer): item_node = self._meta_item_to_xml(xml_doc, item_key, item_value) xml_doc.appendChild(item_node) self._add_xmlns(item_node) - return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') + return xml_doc.toxml('UTF-8') def show(self, meta_item_dict): return self._meta_item_to_xml_string(meta_item_dict['meta']) diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index d4f42bbf5..d8f9cdbac 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -18,6 +18,7 @@ from webob import exc from nova import compute +from nova.api.openstack import common from nova.api.openstack import wsgi from nova import exception from nova import quota @@ -31,36 +32,37 @@ class Controller(object): super(Controller, self).__init__() def _get_metadata(self, context, server_id): - metadata = self.compute_api.get_instance_metadata(context, server_id) + try: + meta = self.compute_api.get_instance_metadata(context, server_id) + except exception.InstanceNotFound: + msg = _('Server does not exist') + raise exc.HTTPNotFound(explanation=msg) + meta_dict = {} - for key, value in metadata.iteritems(): + for key, value in meta.iteritems(): meta_dict[key] = value - return dict(metadata=meta_dict) - - def _check_body(self, body): - if body == None or body == "": - expl = _('No Request Body') - raise exc.HTTPBadRequest(explanation=expl) + return meta_dict def index(self, req, server_id): """ Returns the list of metadata for a given instance """ context = req.environ['nova.context'] - try: - return self._get_metadata(context, server_id) - except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() - raise exc.HTTPNotFound(explanation=msg) + return {'metadata': self._get_metadata(context, server_id)} def create(self, req, server_id, body): - self._check_body(body) + try: + metadata = body['metadata'] + except (KeyError, TypeError): + msg = _("Malformed request body") + raise exc.HTTPBadRequest(esplanation=msg) + context = req.environ['nova.context'] - metadata = body.get('metadata') + try: self.compute_api.update_or_create_instance_metadata(context, server_id, metadata) except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() + msg = _('Server does not exist') raise exc.HTTPNotFound(explanation=msg) except quota.QuotaError as error: @@ -69,51 +71,80 @@ class Controller(object): return body def update(self, req, server_id, id, body): - self._check_body(body) - context = req.environ['nova.context'] - if not id in body: + try: + meta_item = body['meta'] + except (TypeError, KeyError): + expl = _('Malformed request body') + raise exc.HTTPBadRequest(explanation=expl) + + try: + meta_value = meta_item.pop(id) + except (AttributeError, KeyError): expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) - if len(body) > 1: + + if len(meta_item) > 0: expl = _('Request body contains too many items') raise exc.HTTPBadRequest(explanation=expl) + + context = req.environ['nova.context'] + self._set_instance_metadata(context, server_id, meta_item) + + return {'meta': {id: meta_value}} + + def update_all(self, req, server_id, body): + try: + metadata = body['metadata'] + except (TypeError, KeyError): + expl = _('Malformed request body') + raise exc.HTTPBadRequest(explanation=expl) + + context = req.environ['nova.context'] + self._set_instance_metadata(context, server_id, metadata) + + return {'metadata': metadata} + + def _set_instance_metadata(self, context, server_id, metadata): try: self.compute_api.update_or_create_instance_metadata(context, server_id, - body) + metadata) except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() + msg = _('Server does not exist') raise exc.HTTPNotFound(explanation=msg) + except ValueError: + msg = _("Malformed request body") + raise exc.HTTPBadRequest(explanation=msg) + except quota.QuotaError as error: self._handle_quota_error(error) - return body - def show(self, req, server_id, id): """ Return a single metadata item """ context = req.environ['nova.context'] - try: - data = self._get_metadata(context, server_id) - except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() - raise exc.HTTPNotFound(explanation=msg) + data = self._get_metadata(context, server_id) try: - return {id: data['metadata'][id]} + return {'meta': {id: data[id]}} except KeyError: - msg = _("metadata item %s was not found" % (id)) + msg = _("Metadata item was not found") raise exc.HTTPNotFound(explanation=msg) def delete(self, req, server_id, id): """ Deletes an existing metadata """ context = req.environ['nova.context'] + + metadata = self._get_metadata(context, server_id) + try: - self.compute_api.delete_instance_metadata(context, server_id, id) - except exception.InstanceNotFound: - msg = _('Server %(server_id)s does not exist') % locals() + meta_key = metadata[id] + except KeyError: + msg = _("Metadata item was not found") raise exc.HTTPNotFound(explanation=msg) + self.compute_api.delete_instance_metadata(context, server_id, meta_key) + def _handle_quota_error(self, error): """Reraise quota errors as api-specific http exceptions.""" if error.code == "MetadataLimitExceeded": @@ -122,10 +153,16 @@ class Controller(object): def create_resource(): - body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), + headers_serializer = common.MetadataHeadersSerializer() + + body_deserializers = { + 'application/xml': common.MetadataXMLDeserializer(), } - serializer = wsgi.ResponseSerializer(body_serializers) + body_serializers = { + 'application/xml': common.MetadataXMLSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) + deserializer = wsgi.RequestDeserializer(body_deserializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(Controller(), deserializer, serializer) -- cgit From 572847f9eb43ce23190566439118547ae6d6a992 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 11:53:09 -0400 Subject: pep8 --- nova/api/openstack/server_metadata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index d8f9cdbac..f25d36535 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -72,13 +72,13 @@ class Controller(object): def update(self, req, server_id, id, body): try: - meta_item = body['meta'] + meta_item = body['meta'] except (TypeError, KeyError): expl = _('Malformed request body') raise exc.HTTPBadRequest(explanation=expl) try: - meta_value = meta_item.pop(id) + meta_value = meta_item.pop(id) except (AttributeError, KeyError): expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) @@ -94,7 +94,7 @@ class Controller(object): def update_all(self, req, server_id, body): try: - metadata = body['metadata'] + metadata = body['metadata'] except (TypeError, KeyError): expl = _('Malformed request body') raise exc.HTTPBadRequest(explanation=expl) -- cgit From 5ca4d3a88f1dd758c4ab6133e26cf2f8b05a8339 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 12:50:52 -0400 Subject: pep8 --- nova/api/openstack/versions.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 9909d8d0d..4d40274b3 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -28,7 +28,7 @@ ATOM_XMLNS = "http://www.w3.org/2005/Atom" OS_XMLNS_BASE = "http://docs.openstack.org/common/api/" VERSIONS = { "v1.0": { - "version" : { + "version": { "id": "v1.0", "status": "DEPRECATED", "updated": "2011-01-21T11:33:21Z", @@ -52,18 +52,18 @@ VERSIONS = { ], "media-types": [ { - "base" : "application/xml", - "type" : "application/vnd.openstack.compute-v1.0+xml" + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.0+xml" }, { - "base" : "application/json", - "type" : "application/vnd.openstack.compute-v1.0+json" + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.0+json" } ], }, }, "v1.1": { - "version" : { + "version": { "id": "v1.1", "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", @@ -87,12 +87,12 @@ VERSIONS = { ], "media-types": [ { - "base" : "application/xml", - "type" : "application/vnd.openstack.compute-v1.1+xml" + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.1+xml" }, { - "base" : "application/json", - "type" : "application/vnd.openstack.compute-v1.1+json" + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.1+json" } ], }, -- cgit From 0300c952d925ccaad2d3d4191d87c08656d4b413 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Jul 2011 12:54:12 -0400 Subject: removed unused import --- nova/api/openstack/image_metadata.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index da753dee5..aaf64a123 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -22,7 +22,6 @@ from nova import image from nova import quota from nova import utils from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import wsgi -- cgit From 094b9845500e28d315f70aa1fbc37b75c143d0c0 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 27 Jul 2011 10:34:58 -0700 Subject: improved the code per peer review --- nova/api/openstack/contrib/floating_ips.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index a02f34b8c..3d8049324 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -27,9 +27,9 @@ from nova.api.openstack import extensions def _translate_floating_ip_view(floating_ip): result = {'id': floating_ip['id'], 'ip': floating_ip['address']} - if 'fixed_ip' in floating_ip and floating_ip['fixed_ip']: + try: result['fixed_ip'] = floating_ip['fixed_ip']['address'] - else: + except (TypeError, KeyError): result['fixed_ip'] = None if 'instance' in floating_ip: result['instance_id'] = floating_ip['instance']['id'] -- cgit From 9a84b87ae04dc5220f95992d9a6c4e210fbc374f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 13:59:53 -0400 Subject: fixed issue with factory for Versions Resource --- nova/api/openstack/versions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 4d40274b3..c39e9dae7 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -104,9 +104,7 @@ class Versions(wsgi.Resource): @classmethod def factory(cls, global_config, **local_config): """Paste factory.""" - def _factory(app): - return cls(app, **local_config) - return _factory + return cls() def __init__(self): metadata = { -- cgit From f1830708f823a9de9c2f1cd24af5bed80302788f Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 27 Jul 2011 11:08:44 -0700 Subject: Some work on testing. Two cases related to lp816713 have some coverage already: using an id as an imageRef (test_create_instance_v1_1_local_href), and using a nova href as a url (test_create_instance_v1_1) --- nova/api/openstack/create_instance_helper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 0e6c0a87c..234c16bcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -89,7 +89,9 @@ class CreateInstanceHelper(object): image_href = self.controller._image_ref_from_req_data(body) # If the image href was generated by nova api, strip image_href # down to an id and use the default glance connection params - if image_href.startswith(req.application_url): + + if isinstance(image_href, basestring) and\ + image_href.startswith(req.application_url): image_href = image_href.split('/').pop() try: image_service, image_id = nova.image.get_image_service(image_href) -- cgit From 22beaf8802fdc44242f4a96e291c4fbb60af0e3a Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 14:24:35 -0400 Subject: make atom+xml accept header be ignored on 300 responses in the VersionsRequestDeserializer --- nova/api/openstack/versions.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index c39e9dae7..e063ff272 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -206,6 +206,14 @@ class VersionV11(object): class VersionsRequestDeserializer(wsgi.RequestDeserializer): + def get_expected_content_type(self, request): + supported_content_types = list(self.supported_content_types) + if request.path != '/': + # Remove atom+xml accept type for 300 responses + del supported_content_types[2] + + return request.best_match_content_type(supported_content_types) + def get_action_args(self, request_environment): """Parse dictionary created by routes library.""" args = {} -- cgit From c20a4845afc47d124017de698657c1713dc11e7f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 14:35:47 -0400 Subject: fixed xmlns issue --- nova/api/openstack/versions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index e063ff272..b38c48939 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -25,7 +25,7 @@ from nova.api.openstack import wsgi ATOM_XMLNS = "http://www.w3.org/2005/Atom" -OS_XMLNS_BASE = "http://docs.openstack.org/common/api/" +OS_XMLNS_BASE = "http://docs.openstack.org/common/api/v1.0" VERSIONS = { "v1.0": { "version": { @@ -235,7 +235,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions, name="versions", xmlns=None): root = self._xml_doc.createElement(name) - root.setAttribute("xmlns", "%sv1.0" % OS_XMLNS_BASE) + root.setAttribute("xmlns", OS_XMLNS_BASE) root.setAttribute("xmlns:atom", ATOM_XMLNS) for version in versions: @@ -256,7 +256,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _create_version_node(self, version, create_ns=False): version_node = self._xml_doc.createElement('version') if create_ns: - xmlns = "%s%s" % (OS_XMLNS_BASE, version['id']) + xmlns = OS_XMLNS_BASE xmlns_atom = "http://www.w3.org/2005/Atom" version_node.setAttribute('xmlns', xmlns) version_node.setAttribute('xmlns:atom', xmlns_atom) @@ -296,7 +296,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def multi(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['choices'], 'choices', - xmlns="%sv1.0" % OS_XMLNS_BASE) + xmlns=OS_XMLNS_BASE) return self.to_xml_string(node) -- cgit From b8183e11e56781fce27ec1af261c5e53bca78ca5 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Jul 2011 15:15:48 -0400 Subject: change local variable name --- nova/api/openstack/views/servers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index c90801724..659a43522 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -225,10 +225,10 @@ class ViewBuilderV11(ViewBuilder): return os.path.join(common.remove_version_from_href(self.base_url), "servers", str(server_id)) - def _convert_timeformat(self, time): + def _convert_timeformat(self, date_time): """Converts the given time into the common time format - :param time: should be a datetime + :param date_time: the datetime object to convert """ - return time.strftime(utils.TIME_FORMAT) + return date_time.strftime(utils.TIME_FORMAT) -- cgit From 2ac60cd773bb25e19b50c082e0860b1c495d1527 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 15:29:15 -0400 Subject: update everything to use global VERSIONS --- nova/api/openstack/versions.py | 49 +++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 32 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index b38c48939..32ee64339 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -143,20 +143,14 @@ class Versions(wsgi.Resource): return self._versions_multi_choice(request) def _versions_list(self, request): - version_objs = [ - { - "id": "v1.1", - "status": "CURRENT", - #TODO(wwolf) get correct value for these - "updated": "2011-07-18T11:30:00Z", - }, - { - "id": "v1.0", - "status": "DEPRECATED", - #TODO(wwolf) get correct value for these - "updated": "2010-10-09T11:30:00Z", - }, - ] + version_objs = [] + for version in VERSIONS: + version = VERSIONS[version]['version'] + version_objs.append({ + "id": version['id'], + "status": version['status'], + "updated": version['updated'], + }) builder = nova.api.openstack.views.versions.get_view_builder(request) versions = [builder.build(version) for version in version_objs] @@ -164,28 +158,19 @@ class Versions(wsgi.Resource): def _versions_multi_choice(self, request): #TODO - version_objs = [ - { - "id": "v1.1", - "status": "CURRENT", + version_objs = [] + for version in VERSIONS: + version = VERSIONS[version]['version'] + version_objs.append({ + "id": version['id'], + "status": version['status'], "links": [ { - "rel": "self", + "rel": "self" } ], - "media-types": VERSIONS['v1.1']['version']['media-types'], - }, - { - "id": "v1.0", - "status": "DEPRECATED", - "links": [ - { - "rel": "self", - } - ], - "media-types": VERSIONS['v1.0']['version']['media-types'], - }, - ] + "media-types": version['media-types'] + }) builder = nova.api.openstack.views.versions.get_view_builder(request) choices = [ -- cgit From f2d8e91b83ff3a3bd1e2f3c53c25a418a578cd27 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 16:34:02 -0400 Subject: moved rest of build logic into builder --- nova/api/openstack/versions.py | 42 +++----------------------------- nova/api/openstack/views/versions.py | 46 ++++++++++++++++++++++++------------ 2 files changed, 34 insertions(+), 54 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 32ee64339..fd0ee46b7 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -135,49 +135,13 @@ class Versions(wsgi.Resource): def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" + builder = nova.api.openstack.views.versions.get_view_builder(request) if request.path == '/': # List Versions - return self._versions_list(request) + return builder.build(VERSIONS) else: # Versions Multiple Choice - return self._versions_multi_choice(request) - - def _versions_list(self, request): - version_objs = [] - for version in VERSIONS: - version = VERSIONS[version]['version'] - version_objs.append({ - "id": version['id'], - "status": version['status'], - "updated": version['updated'], - }) - - builder = nova.api.openstack.views.versions.get_view_builder(request) - versions = [builder.build(version) for version in version_objs] - return dict(versions=versions) - - def _versions_multi_choice(self, request): - #TODO - version_objs = [] - for version in VERSIONS: - version = VERSIONS[version]['version'] - version_objs.append({ - "id": version['id'], - "status": version['status'], - "links": [ - { - "rel": "self" - } - ], - "media-types": version['media-types'] - }) - - builder = nova.api.openstack.views.versions.get_view_builder(request) - choices = [ - builder.build_choices(version, request) - for version in version_objs] - - return dict(choices=choices) + return builder.build_choices(VERSIONS, request) class VersionV10(object): diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 97e35c983..87ec251e6 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -31,21 +31,37 @@ class ViewBuilder(object): """ self.base_url = base_url - def build_choices(self, version_data, request): - version_data['links'][0]['href'] = self._build_versioned_link(request, - version_data['id']) - return version_data - - def build(self, version_data): - """Generic method used to generate a version entity.""" - version = { - "id": version_data["id"], - "status": version_data["status"], - "updated": version_data["updated"], - "links": self._build_links(version_data), - } - - return version + def build_choices(self, VERSIONS, request): + version_objs = [] + for version in VERSIONS: + version = VERSIONS[version]['version'] + version_objs.append({ + "id": version['id'], + "status": version['status'], + "links": [ + { + "rel": "self", + "href": self._build_versioned_link(request, + version['id']) + } + ], + "media-types": version['media-types'] + }) + + return dict(choices=version_objs) + + def build(self, VERSIONS): + version_objs = [] + for version in VERSIONS: + version = VERSIONS[version]['version'] + version_objs.append({ + "id": version['id'], + "status": version['status'], + "updated": version['updated'], + "links": self._build_links(version), + }) + + return dict(versions=version_objs) def _build_versioned_link(self, req, version): return '%s://%s/%s%s' % (req.scheme, req.host, version, req.path) -- cgit From 9c220b1c4547ad2cdd6110fa029b6f9478bae99f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 16:54:09 -0400 Subject: added test for accept header of atom+xml on 300 responses to make sure it defaults back to json, and reworked some of the logic to make how this happens clearer --- nova/api/openstack/versions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index fd0ee46b7..02b0e1df8 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -159,7 +159,8 @@ class VersionsRequestDeserializer(wsgi.RequestDeserializer): supported_content_types = list(self.supported_content_types) if request.path != '/': # Remove atom+xml accept type for 300 responses - del supported_content_types[2] + if 'application/atom+xml' in supported_content_types: + supported_content_types.remove('application/atom+xml') return request.best_match_content_type(supported_content_types) -- cgit From 77d06c7c82bfafd956f1108b2adbcb378628511f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 17:09:17 -0400 Subject: utilize _create_link_nodes base class function --- nova/api/openstack/versions.py | 11 +++-------- nova/api/openstack/wsgi.py | 2 ++ 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 02b0e1df8..928cf467a 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -220,14 +220,9 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): media_types = self._create_media_types(version['media-types']) version_node.appendChild(media_types) - for link in version['links']: - link_node = self._xml_doc.createElement('atom:link') - link_node.setAttribute('rel', link['rel']) - link_node.setAttribute('href', link['href']) - if 'type' in link: - link_node.setAttribute('type', link['type']) - - version_node.appendChild(link_node) + link_nodes = self._create_link_nodes(self._xml_doc, version['links']) + for link in link_nodes: + version_node.appendChild(link) return version_node diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index a28443d12..ca502021d 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -387,6 +387,8 @@ class XMLDictSerializer(DictSerializer): link_node = xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) + if 'type' in link: + link_node.setAttribute('type', link['type']) link_nodes.append(link_node) return link_nodes -- cgit From f9ff78a5ac5f83d789334c36bebfce62af0ea406 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 27 Jul 2011 17:20:42 -0400 Subject: refactoring MetadataXMLDeserializer in wsgi/common --- nova/api/openstack/common.py | 12 +++++++++++- nova/api/openstack/create_instance_helper.py | 7 +++++-- nova/api/openstack/wsgi.py | 13 ------------- 3 files changed, 16 insertions(+), 16 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 24c82035a..a99951764 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -196,7 +196,17 @@ def get_version_from_href(href): return version -class MetadataXMLDeserializer(wsgi.MetadataXMLDeserializer): +class MetadataXMLDeserializer(wsgi.XMLDeserializer): + + def extract_metadata(self, metadata_node): + """Marshal the metadata attribute of a parsed request""" + if metadata_node is None: + return None + metadata = {} + for meta_node in self.find_children_named(metadata_node, "meta"): + key = meta_node.getAttribute("key") + metadata[key] = self.extract_text(meta_node) + return metadata def _extract_metadata_container(self, datastring): dom = minidom.parseString(datastring) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index f8317565e..70532cf79 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -28,6 +28,7 @@ from nova import quota from nova import utils from nova.compute import instance_types +from nova.api.openstack import common from nova.api.openstack import wsgi from nova.auth import manager as auth_manager @@ -285,7 +286,7 @@ class CreateInstanceHelper(object): return password -class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): +class ServerXMLDeserializer(wsgi.XMLDeserializer): """ Deserializer to handle xml-formatted server create requests. @@ -293,6 +294,8 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): and personality attributes """ + metadata_deserializer = common.MetadataXMLDeserializer() + def create(self, string): """Deserialize an xml-formatted server create request""" dom = minidom.parseString(string) @@ -307,7 +310,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) metadata_node = self.find_first_child_named(server_node, "metadata") - metadata = self.extract_metadata(metadata_node) + metadata = self.metadata_deserializer.extract_metadata(metadata_node) if metadata is not None: server["metadata"] = metadata personality = self._extract_personality(server_node) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index a28443d12..d10424d79 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -161,19 +161,6 @@ class XMLDeserializer(TextDeserializer): return {'body': self._from_xml(datastring)} -class MetadataXMLDeserializer(XMLDeserializer): - - def extract_metadata(self, metadata_node): - """Marshal the metadata attribute of a parsed request""" - if metadata_node is None: - return None - metadata = {} - for meta_node in self.find_children_named(metadata_node, "meta"): - key = meta_node.getAttribute("key") - metadata[key] = self.extract_text(meta_node) - return metadata - - class RequestHeadersDeserializer(ActionDispatcher): """Default request headers deserializer""" -- cgit From 03aac3ffd546ab1528b73ee36c8632f30ed8af2f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 27 Jul 2011 18:07:01 -0400 Subject: use ATOM_XMLNS everywhere --- nova/api/openstack/versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 928cf467a..95681b788 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -207,7 +207,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): version_node = self._xml_doc.createElement('version') if create_ns: xmlns = OS_XMLNS_BASE - xmlns_atom = "http://www.w3.org/2005/Atom" + xmlns_atom = ATOM_XMLNS version_node.setAttribute('xmlns', xmlns) version_node.setAttribute('xmlns:atom', xmlns_atom) -- cgit From 71414e65333692956023647b55be06de6a73f11f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 28 Jul 2011 14:59:28 -0400 Subject: use wsgi XMLNS/ATOM vars --- nova/api/openstack/versions.py | 14 ++++++-------- nova/api/openstack/wsgi.py | 1 + 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 95681b788..292043b94 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -24,8 +24,6 @@ import nova.api.openstack.views.versions from nova.api.openstack import wsgi -ATOM_XMLNS = "http://www.w3.org/2005/Atom" -OS_XMLNS_BASE = "http://docs.openstack.org/common/api/v1.0" VERSIONS = { "v1.0": { "version": { @@ -185,8 +183,8 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions, name="versions", xmlns=None): root = self._xml_doc.createElement(name) - root.setAttribute("xmlns", OS_XMLNS_BASE) - root.setAttribute("xmlns:atom", ATOM_XMLNS) + root.setAttribute("xmlns", wsgi.XMLNS_V11) + root.setAttribute("xmlns:atom", wsgi.XMLNS_ATOM) for version in versions: root.appendChild(self._create_version_node(version)) @@ -206,8 +204,8 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _create_version_node(self, version, create_ns=False): version_node = self._xml_doc.createElement('version') if create_ns: - xmlns = OS_XMLNS_BASE - xmlns_atom = ATOM_XMLNS + xmlns = wsgi.XMLNS_V11 + xmlns_atom = wsgi.XMLNS_ATOM version_node.setAttribute('xmlns', xmlns) version_node.setAttribute('xmlns:atom', xmlns_atom) @@ -241,7 +239,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def multi(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['choices'], 'choices', - xmlns=OS_XMLNS_BASE) + xmlns=wsgi.XMLNS_V11) return self.to_xml_string(node) @@ -257,7 +255,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def __init__(self, metadata=None, xmlns=None): self.metadata = metadata or {} if not xmlns: - self.xmlns = ATOM_XMLNS + self.xmlns = wsgi.XMLNS_ATOM else: self.xmlns = xmlns diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index ca502021d..c6ece7d45 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -13,6 +13,7 @@ from nova import wsgi XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1' + XMLNS_ATOM = 'http://www.w3.org/2005/Atom' LOG = logging.getLogger('nova.api.openstack.wsgi') -- cgit From 1c2ac1e7646d1432f57104c6ee3d1fa434387741 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 28 Jul 2011 15:48:30 -0400 Subject: refactoring and make self links correct (not hard coded) --- nova/api/openstack/versions.py | 136 +++++++++++++++++------------------ nova/api/openstack/views/versions.py | 33 +++++---- 2 files changed, 88 insertions(+), 81 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 292043b94..b546462d4 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -26,74 +26,70 @@ from nova.api.openstack import wsgi VERSIONS = { "v1.0": { - "version": { - "id": "v1.0", - "status": "DEPRECATED", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "http://servers.api.openstack.org/v1.0/" - }, - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" - }, - ], - "media-types": [ - { - "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.0+xml" - }, - { - "base": "application/json", - "type": "application/vnd.openstack.compute-v1.0+json" - } - ], - }, + "id": "v1.0", + "status": "DEPRECATED", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.0/application.wadl" + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.0+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.0+json" + } + ], }, "v1.1": { - "version": { - "id": "v1.1", - "status": "CURRENT", - "updated": "2011-01-21T11:33:21Z", - "links": [ - { - "rel": "self", - "href": "http://servers.api.openstack.org/v1.1/" - }, - { - "rel": "describedby", - "type": "application/pdf", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" - }, - { - "rel": "describedby", - "type": "application/vnd.sun.wadl+xml", - "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" - }, - ], - "media-types": [ - { - "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.1+xml" - }, - { - "base": "application/json", - "type": "application/vnd.openstack.compute-v1.1+json" - } - ], - }, + "id": "v1.1", + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "links": [ + { + "rel": "self", + "href": "" + }, + { + "rel": "describedby", + "type": "application/pdf", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/cs-devguide-20110125.pdf" + }, + { + "rel": "describedby", + "type": "application/vnd.sun.wadl+xml", + "href": "http://docs.rackspacecloud.com/" + "servers/api/v1.1/application.wadl" + }, + ], + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute-v1.1+xml" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute-v1.1+json" + } + ], }, } @@ -136,7 +132,7 @@ class Versions(wsgi.Resource): builder = nova.api.openstack.views.versions.get_view_builder(request) if request.path == '/': # List Versions - return builder.build(VERSIONS) + return builder.build_versions(VERSIONS) else: # Versions Multiple Choice return builder.build_choices(VERSIONS, request) @@ -144,12 +140,14 @@ class Versions(wsgi.Resource): class VersionV10(object): def show(self, req): - return VERSIONS['v1.0'] + builder = nova.api.openstack.views.versions.get_view_builder(req) + return builder.build_version(VERSIONS['v1.0']) class VersionV11(object): def show(self, req): - return VERSIONS['v1.1'] + builder = nova.api.openstack.views.versions.get_view_builder(req) + return builder.build_version(VERSIONS['v1.1']) class VersionsRequestDeserializer(wsgi.RequestDeserializer): diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 87ec251e6..7673ffe9e 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -31,18 +31,17 @@ class ViewBuilder(object): """ self.base_url = base_url - def build_choices(self, VERSIONS, request): + def build_choices(self, VERSIONS, req): version_objs = [] for version in VERSIONS: - version = VERSIONS[version]['version'] + version = VERSIONS[version] version_objs.append({ "id": version['id'], "status": version['status'], "links": [ { "rel": "self", - "href": self._build_versioned_link(request, - version['id']) + "href": self.generate_href(version['id'], req.path) } ], "media-types": version['media-types'] @@ -50,10 +49,10 @@ class ViewBuilder(object): return dict(choices=version_objs) - def build(self, VERSIONS): + def build_versions(self, versions): version_objs = [] - for version in VERSIONS: - version = VERSIONS[version]['version'] + for version in versions: + version = versions[version] version_objs.append({ "id": version['id'], "status": version['status'], @@ -63,8 +62,13 @@ class ViewBuilder(object): return dict(versions=version_objs) - def _build_versioned_link(self, req, version): - return '%s://%s/%s%s' % (req.scheme, req.host, version, req.path) + def build_version(self, version): + + for link in version['links']: + if link['rel'] == 'self': + link['href'] = self.base_url.rstrip('/') + '/' + + return dict(version=version) def _build_links(self, version_data): """Generate a container of links that refer to the provided version.""" @@ -73,12 +77,17 @@ class ViewBuilder(object): links = [ { "rel": "self", - "href": href, + "href": href }, ] return links - def generate_href(self, version_number): + def generate_href(self, version_number, path=None): """Create an url that refers to a specific version_number.""" - return os.path.join(self.base_url, version_number) + '/' + version_number = version_number.strip('/') + if path: + path = path.strip('/') + return os.path.join(self.base_url, version_number, path) + else: + return os.path.join(self.base_url, version_number) + '/' -- cgit From 8141ef4139fbf8512150ce970cea4dc4bee22e1a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 28 Jul 2011 16:21:12 -0400 Subject: moving server backup to /servers//action instead of POST /images --- nova/api/openstack/create_instance_helper.py | 12 ++- nova/api/openstack/images.py | 112 +++------------------- nova/api/openstack/servers.py | 133 +++++++++++++++++---------- 3 files changed, 111 insertions(+), 146 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 73ff191e8..e165899e7 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -300,6 +300,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): action_deserializer = { 'createImage': self._action_create_image, + 'createBackup': self._action_create_image, }.get(action_name, self.default) action_data = action_deserializer(action_node) @@ -308,7 +309,16 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): def _action_create_image(self, node): data = {} - attributes = ['name', 'image_type', 'backup_type', 'rotation'] + value = node.getAttribute('name') + if value: + data['name'] = value + metadata_node = self.find_first_child_named(node, 'metadata') + data['metadata'] = self.extract_metadata(metadata_node) + return data + + def _action_create_image(self, node): + data = {} + attributes = ['name', 'backup_type', 'rotation'] for attribute in attributes: value = node.getAttribute(attribute) if value: diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 517a51662..b3e16c997 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -102,72 +102,27 @@ class Controller(object): """Indicates that you must use a Controller subclass.""" raise NotImplementedError() - def _server_id_from_req(self, req, data): - raise NotImplementedError() - - def _get_extra_properties(self, req, data): - return {} - class ControllerV10(Controller): """Version 1.0 specific controller logic.""" def create(self, req, body): - """Snapshot or backup a server instance and save the image. - - Images now have an `image_type` associated with them, which can be - 'snapshot' or the backup type, like 'daily' or 'weekly'. - - If the image_type is backup-like, then the rotation factor can be - included and that will cause the oldest backups that exceed the - rotation factor to be deleted. - - :param req: `wsgi.Request` object - """ - def get_param(param): - try: - return body["image"][param] - except KeyError: - raise webob.exc.HTTPBadRequest(explanation="Missing required " - "param: %s" % param) - - context = req.environ['nova.context'] - content_type = req.get_content_type() - - if not body: - raise webob.exc.HTTPBadRequest() - - image_type = body["image"].get("image_type", "snapshot") + """Snapshot a server instance and save the image.""" + try: + image = body["image"] + except (KeyError, TypeError): + msg = _("Invalid image entity") + raise webob.exc.HTTPBadRequest(explanation=msg) try: - server_id = self._server_id_from_req(req, body) - except KeyError: - raise webob.exc.HTTPBadRequest() - - image_name = get_param("name") - props = self._get_extra_properties(req, body) - - if image_type == "snapshot": - image = self._compute_service.snapshot( - context, server_id, image_name, - extra_properties=props) - elif image_type == "backup": - # NOTE(sirp): Unlike snapshot, backup is not a customer facing - # API call; rather, it's used by the internal backup scheduler - if not FLAGS.allow_admin_api: - raise webob.exc.HTTPBadRequest( - explanation="Admin API Required") - - backup_type = get_param("backup_type") - rotation = int(get_param("rotation")) - - image = self._compute_service.backup( - context, server_id, image_name, - backup_type, rotation, extra_properties=props) - else: - LOG.error(_("Invalid image_type '%s' passed") % image_type) - raise webob.exc.HTTPBadRequest(explanation="Invalue image_type: " - "%s" % image_type) + image_name = image["name"] + server_id = image["serverId"] + except KeyError as missing_key: + msg = _("Image entity requires %s") % missing_key + raise webob.exc.HTTPBadRequest(explanation=msg) + + context = req.environ["nova.context"] + image = self._compute_service.snapshot(context, server_id, image_name) return dict(image=self.get_builder(req).build(image, detail=True)) @@ -202,13 +157,6 @@ class ControllerV10(Controller): builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) - def _server_id_from_req(self, req, data): - try: - return data['image']['serverId'] - except KeyError: - msg = _("Expected serverId attribute on server entity.") - raise webob.exc.HTTPBadRequest(explanation=msg) - class ControllerV11(Controller): """Version 1.1 specific controller logic.""" @@ -246,38 +194,6 @@ class ControllerV11(Controller): builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) - def _server_id_from_req(self, req, data): - try: - server_ref = data['image']['serverRef'] - except KeyError: - msg = _("Expected serverRef attribute on server entity.") - raise webob.exc.HTTPBadRequest(explanation=msg) - - if not server_ref.startswith('http'): - return server_ref - - passed = urlparse.urlparse(server_ref) - expected = urlparse.urlparse(req.application_url) - version = expected.path.split('/')[1] - expected_prefix = "/%s/servers/" % version - _empty, _sep, server_id = passed.path.partition(expected_prefix) - scheme_ok = passed.scheme == expected.scheme - host_ok = passed.hostname == expected.hostname - port_ok = (passed.port == expected.port or - passed.port == FLAGS.osapi_port) - if not (scheme_ok and port_ok and host_ok and server_id): - msg = _("serverRef must match request url") - raise webob.exc.HTTPBadRequest(explanation=msg) - - return server_id - - def _get_extra_properties(self, req, data): - server_ref = data['image']['serverRef'] - if not server_ref.startswith('http'): - server_ref = os.path.join(req.application_url, 'servers', - server_ref) - return {'instance_ref': server_ref} - def create(self, *args, **kwargs): raise webob.exc.HTTPMethodNotAllowed() diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 0cc81009b..fafde1c15 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -166,11 +166,79 @@ class Controller(object): 'createImage': self._action_create_image, } + if FLAGS.allow_admin_api: + admin_actions = { + 'createBackup': self._action_create_backup, + } + self.actions.update(admin_actions) + for key in self.actions.keys(): if key in body: return self.actions[key](body, req, id) + raise exc.HTTPNotImplemented() + def _action_create_backup(self, input_dict, req, instance_id): + """Backup a server instance. + + Images now have an `image_type` associated with them, which can be + 'snapshot' or the backup type, like 'daily' or 'weekly'. + + If the image_type is backup-like, then the rotation factor can be + included and that will cause the oldest backups that exceed the + rotation factor to be deleted. + + """ + entity = input_dict["createBackup"] + + try: + image_name = entity["name"] + backup_type = entity["backup_type"] + rotation = entity["rotation"] + + except KeyError as missing_key: + msg = _("createBackup entity requires %s attribute") % missing_key + raise webob.exc.HTTPBadRequest(explanation=msg) + + except TypeError: + msg = _("Malformed createBackup entity") + raise webob.exc.HTTPBadRequest(explanation=msg) + + try: + rotation = int(rotation) + except ValueError: + msg = _("createBackup attribute 'rotation' must be an integer") + raise webob.exc.HTTPBadRequest(explanation=msg) + + # preserve link to server in image properties + server_ref = os.path.join(req.application_url, + 'servers', + str(instance_id)) + props = {'instance_ref': server_ref} + + metadata = entity.get('metadata', {}) + try: + props.update(metadata) + except ValueError: + msg = _("Invalid metadata") + raise webob.exc.HTTPBadRequest(explanation=msg) + + context = req.environ["nova.context"] + image = self.compute_api.backup(context, + instance_id, + image_name, + backup_type, + rotation, + extra_properties=props) + + # build location of newly-created image entity + image_id = str(image['id']) + image_ref = os.path.join(req.application_url, 'images', image_id) + + resp = webob.Response(status_int=202) + resp.headers['Location'] = image_ref + return resp + def _action_create_image(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -599,30 +667,22 @@ class ControllerV11(Controller): return webob.Response(status_int=202) - def _action_create_image(self, input_dict, req, instance_id): - """Snapshot or backup a server instance and save the image. - - Images now have an `image_type` associated with them, which can be - 'snapshot' or the backup type, like 'daily' or 'weekly'. - If the image_type is backup-like, then the rotation factor can be - included and that will cause the oldest backups that exceed the - rotation factor to be deleted. - """ - entity = input_dict.get('createImage', {}) + def _action_create_image(self, input_dict, req, instance_id): + """Snapshot a server instance.""" + entity = input_dict.get("createImage", {}) - def get_param(param): - try: - return entity[param] - except KeyError: - msg = _("Missing required param: %s") % param - raise webob.exc.HTTPBadRequest(explanation=msg) + try: + image_name = entity["name"] - context = req.environ['nova.context'] + except KeyError: + msg = _("createImage entity requires name attribute") + raise webob.exc.HTTPBadRequest(explanation=msg) - image_name = get_param("name") - image_type = entity.get("image_type", "snapshot") + except TypeError: + msg = _("Malformed createImage entity") + raise webob.exc.HTTPBadRequest(explanation=msg) # preserve link to server in image properties server_ref = os.path.join(req.application_url, @@ -637,36 +697,15 @@ class ControllerV11(Controller): msg = _("Invalid metadata") raise webob.exc.HTTPBadRequest(explanation=msg) - if image_type == "snapshot": - image = self.compute_api.snapshot(context, - instance_id, - image_name, - extra_properties=props) - - elif image_type == "backup": - # NOTE(sirp): Unlike snapshot, backup is not a customer facing - # API call; rather, it's used by the internal backup scheduler - if not FLAGS.allow_admin_api: - msg = _("Admin API Required") - raise webob.exc.HTTPBadRequest(explanation=msg) - - backup_type = get_param("backup_type") - rotation = int(get_param("rotation")) - - image = self.compute_api.backup(context, - instance_id, - image_name, - backup_type, - rotation, - extra_properties=props) - else: - msg = _("Invalid image_type '%s'") % image_type - raise webob.exc.HTTPBadRequest(explanation=msg) + context = req.environ['nova.context'] + image = self.compute_api.snapshot(context, + instance_id, + image_name, + extra_properties=props) # build location of newly-created image entity - image_ref = os.path.join(req.application_url, - 'images', - str(image['id'])) + image_id = str(image['id']) + image_ref = os.path.join(req.application_url, 'images', image_id) resp = webob.Response(status_int=202) resp.headers['Location'] = image_ref -- cgit From 0a7e19481849f451f04063d3d2fc45b8f3328119 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 28 Jul 2011 16:33:14 -0400 Subject: pep8 --- nova/api/openstack/servers.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fafde1c15..636d79b66 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -667,8 +667,6 @@ class ControllerV11(Controller): return webob.Response(status_int=202) - - def _action_create_image(self, input_dict, req, instance_id): """Snapshot a server instance.""" entity = input_dict.get("createImage", {}) -- cgit From 54f652bbffaf8edf9ccfe35e1e1b15c20327340a Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 28 Jul 2011 20:59:07 -0400 Subject: fixed pep8 issues and removed unnecessary factory function --- nova/api/openstack/versions.py | 15 +++++---------- nova/api/openstack/views/versions.py | 3 +-- 2 files changed, 6 insertions(+), 12 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index b546462d4..40e966c5f 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -38,13 +38,13 @@ VERSIONS = { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" + "servers/api/v1.0/cs-devguide-20110125.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" + "servers/api/v1.0/application.wadl" }, ], "media-types": [ @@ -71,13 +71,13 @@ VERSIONS = { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" + "servers/api/v1.1/cs-devguide-20110125.pdf" }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" + "servers/api/v1.1/application.wadl" }, ], "media-types": [ @@ -95,12 +95,7 @@ VERSIONS = { class Versions(wsgi.Resource): - @classmethod - def factory(cls, global_config, **local_config): - """Paste factory.""" - return cls() - - def __init__(self): + eef __init__(self): metadata = { "attributes": { "version": ["status", "id"], diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 7673ffe9e..cdf758b77 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -63,7 +63,6 @@ class ViewBuilder(object): return dict(versions=version_objs) def build_version(self, version): - for link in version['links']: if link['rel'] == 'self': link['href'] = self.base_url.rstrip('/') + '/' @@ -77,7 +76,7 @@ class ViewBuilder(object): links = [ { "rel": "self", - "href": href + "href": href, }, ] -- cgit From ad8d33165f52ddf14dc9bd745db00eb039d74af7 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 28 Jul 2011 21:00:55 -0400 Subject: fixed typo --- nova/api/openstack/versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 40e966c5f..607cf6a81 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -95,7 +95,7 @@ VERSIONS = { class Versions(wsgi.Resource): - eef __init__(self): + def __init__(self): metadata = { "attributes": { "version": ["status", "id"], -- cgit From eca19199bdfcc64948f41d7e6b1728cb17b3baa2 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 29 Jul 2011 10:08:20 -0400 Subject: fix more spacing issues, and removed self link from versions template data --- nova/api/openstack/versions.py | 8 -------- nova/api/openstack/views/versions.py | 12 +++++++----- 2 files changed, 7 insertions(+), 13 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 607cf6a81..3ef72b7f6 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -30,10 +30,6 @@ VERSIONS = { "status": "DEPRECATED", "updated": "2011-01-21T11:33:21Z", "links": [ - { - "rel": "self", - "href": "" - }, { "rel": "describedby", "type": "application/pdf", @@ -63,10 +59,6 @@ VERSIONS = { "status": "CURRENT", "updated": "2011-01-21T11:33:21Z", "links": [ - { - "rel": "self", - "href": "" - }, { "rel": "describedby", "type": "application/pdf", diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index cdf758b77..547289034 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import os @@ -63,11 +64,12 @@ class ViewBuilder(object): return dict(versions=version_objs) def build_version(self, version): - for link in version['links']: - if link['rel'] == 'self': - link['href'] = self.base_url.rstrip('/') + '/' - - return dict(version=version) + reval = copy.deepcopy(version) + reval['links'].insert(0, { + "rel": "self", + "href": self.base_url.rstrip('/') + '/', + }) + return dict(version=reval) def _build_links(self, version_data): """Generate a container of links that refer to the provided version.""" -- cgit From 7a165843aa5c1a98b1dbf13dedf556878a3d0435 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 29 Jul 2011 11:06:02 -0400 Subject: Updated resize to call compute API with instance_type identifiers instead of flavor identifiers. Updated tests. --- nova/api/openstack/servers.py | 63 +++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 23 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f6841318d..096fb229c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,11 +17,10 @@ import base64 import traceback from webob import exc -import webob from xml.dom import minidom +import webob from nova import compute -from nova import db from nova import exception from nova import flags from nova import log as logging @@ -29,13 +28,14 @@ from nova import utils from nova.api.openstack import common from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import ips +from nova.api.openstack import wsgi +from nova.compute import instance_types +from nova.scheduler import api as scheduler_api +import nova.api.openstack import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images import nova.api.openstack.views.servers -from nova.api.openstack import wsgi -import nova.api.openstack -from nova.scheduler import api as scheduler_api LOG = logging.getLogger('nova.api.openstack.servers') @@ -438,13 +438,21 @@ class ControllerV10(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ - if 'resize' in input_dict and 'flavorId' in input_dict['resize']: - flavor_id = input_dict['resize']['flavorId'] - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing 'flavorId' argument for resize")) - raise exc.HTTPUnprocessableEntity() + try: + flavor_id = input_dict["resize"]["flavorId"] + except (KeyError, TypeError): + msg = _("Resize requests require 'flavorId' attribute.") + raise exc.HTTPBadRequest(explanation=msg) + + try: + i_type = instance_types.get_instance_type_by_flavor_id(flavor_id) + except exception.FlavorNotFound: + msg = _("Unable to locate requested flavor.") + raise exc.HTTPBadRequest(explanation=msg) + + context = req.environ["nova.context"] + self.compute_api.resize(context, id, i_type["id"]) + return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): @@ -555,17 +563,26 @@ class ControllerV11(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ try: - if 'resize' in input_dict and 'flavorRef' in input_dict['resize']: - flavor_ref = input_dict['resize']['flavorRef'] - flavor_id = common.get_id_from_href(flavor_ref) - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing 'flavorRef' argument for resize")) - raise exc.HTTPUnprocessableEntity() - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - raise exc.HTTPBadRequest() + flavor = input_dict["resize"]["flavor"] + except (KeyError, TypeError): + msg = _("Resize requests require a flavor to resize instance.") + raise exc.HTTPBadRequest(explanation=msg) + + try: + flavor_id = flavor["id"] + except (KeyError, TypeError): + msg = _("Resize flavor requires 'id' attribute.") + raise exc.HTTPBadRequest(explanation=msg) + + try: + i_type = instance_types.get_instance_type_by_flavor_id(flavor_id) + except exception.FlavorNotFound: + msg = _("Unable to locate requested flavor.") + raise exc.HTTPBadRequest(explanation=msg) + + context = req.environ["nova.context"] + self.compute_api.resize(context, id, i_type["id"]) + return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): -- cgit From 50abd79432ff82a23da1934cc4d297c0c5051668 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 29 Jul 2011 11:57:40 -0400 Subject: Oops, I wasn't actually being compatible with the spec here. --- nova/api/openstack/servers.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 096fb229c..30169d450 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -563,19 +563,13 @@ class ControllerV11(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ try: - flavor = input_dict["resize"]["flavor"] + flavor_ref = input_dict["resize"]["flavorRef"] except (KeyError, TypeError): - msg = _("Resize requests require a flavor to resize instance.") + msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) try: - flavor_id = flavor["id"] - except (KeyError, TypeError): - msg = _("Resize flavor requires 'id' attribute.") - raise exc.HTTPBadRequest(explanation=msg) - - try: - i_type = instance_types.get_instance_type_by_flavor_id(flavor_id) + i_type = instance_types.get_instance_type_by_flavor_id(flavor_ref) except exception.FlavorNotFound: msg = _("Unable to locate requested flavor.") raise exc.HTTPBadRequest(explanation=msg) -- cgit From 055a422643fc229ec0e7db3f6dcba9904c5a4f5d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 29 Jul 2011 18:05:31 -0400 Subject: Created exceptions for accepting in OSAPI, and handled them appropriately. --- nova/api/openstack/servers.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 30169d450..e62e4e4f8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -405,6 +405,24 @@ class Controller(object): error=item.error)) return dict(actions=actions) + def resize(self, req, instance_id, flavor_id): + """Begin the resize process with given instance/flavor.""" + context = req.environ["nova.context"] + + try: + self.compute_api.resize(context, instance_id, flavor_id) + except exception.FlavorDoesNotExist: + msg = _("Unable to locate requested flavor.") + raise exc.HTTPBadRequest(explanation=msg) + except exception.CannotResizeToSameSize: + msg = _("Resize requires a change in size.") + raise exc.HTTPBadRequest(explanation=msg) + except exception.CannotResizeToSmallerSize: + msg = _("Resizing to a smaller size is not supported.") + raise exc.HTTPBadRequest(explanation=msg) + + return webob.Response(status_int=202) + class ControllerV10(Controller): @@ -444,16 +462,7 @@ class ControllerV10(Controller): msg = _("Resize requests require 'flavorId' attribute.") raise exc.HTTPBadRequest(explanation=msg) - try: - i_type = instance_types.get_instance_type_by_flavor_id(flavor_id) - except exception.FlavorNotFound: - msg = _("Unable to locate requested flavor.") - raise exc.HTTPBadRequest(explanation=msg) - - context = req.environ["nova.context"] - self.compute_api.resize(context, id, i_type["id"]) - - return webob.Response(status_int=202) + return Controller.resize(self, req, id, flavor_id) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] @@ -568,16 +577,7 @@ class ControllerV11(Controller): msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) - try: - i_type = instance_types.get_instance_type_by_flavor_id(flavor_ref) - except exception.FlavorNotFound: - msg = _("Unable to locate requested flavor.") - raise exc.HTTPBadRequest(explanation=msg) - - context = req.environ["nova.context"] - self.compute_api.resize(context, id, i_type["id"]) - - return webob.Response(status_int=202) + return Controller.resize(self, req, id, flavor_ref) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] -- cgit From a7f0eb04236b15a026654346b47bc434886b9d97 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 29 Jul 2011 18:13:28 -0400 Subject: FlavorNotFound already existed, no need to create another exception. --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e62e4e4f8..f35eec884 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -411,7 +411,7 @@ class Controller(object): try: self.compute_api.resize(context, instance_id, flavor_id) - except exception.FlavorDoesNotExist: + except exception.FlavorNotFound: msg = _("Unable to locate requested flavor.") raise exc.HTTPBadRequest(explanation=msg) except exception.CannotResizeToSameSize: -- cgit From 25a831fd449dbbb7f0c2cdac404d7600a6da9f27 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 1 Aug 2011 18:01:04 -0400 Subject: Controller -> self --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f35eec884..8d2ccc2ad 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -462,7 +462,7 @@ class ControllerV10(Controller): msg = _("Resize requests require 'flavorId' attribute.") raise exc.HTTPBadRequest(explanation=msg) - return Controller.resize(self, req, id, flavor_id) + return self.resize(req, id, flavor_id) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] @@ -577,7 +577,7 @@ class ControllerV11(Controller): msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) - return Controller.resize(self, req, id, flavor_ref) + return self.resize(req, id, flavor_ref) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] -- cgit From 0e5b2b0bfb7f6b0af7f2c962a963830a8691410e Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 1 Aug 2011 18:56:16 -0700 Subject: avoid explicit type checking, per brian waldon's comment --- nova/api/openstack/create_instance_helper.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 234c16bcc..b414b2173 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -90,8 +90,7 @@ class CreateInstanceHelper(object): # If the image href was generated by nova api, strip image_href # down to an id and use the default glance connection params - if isinstance(image_href, basestring) and\ - image_href.startswith(req.application_url): + if str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: image_service, image_id = nova.image.get_image_service(image_href) -- cgit From 804bbc7656080597880e9705532ac161d3124aa4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 2 Aug 2011 10:21:36 -0400 Subject: fixing method naming problem --- nova/api/openstack/create_instance_helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 5a745ded3..ea5d4af73 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -303,7 +303,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): action_deserializer = { 'createImage': self._action_create_image, - 'createBackup': self._action_create_image, + 'createBackup': self._action_create_backup, }.get(action_name, self.default) action_data = action_deserializer(action_node) @@ -319,7 +319,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): data['metadata'] = self.extract_metadata(metadata_node) return data - def _action_create_image(self, node): + def _action_create_backup(self, node): data = {} attributes = ['name', 'backup_type', 'rotation'] for attribute in attributes: -- cgit From 1fc4c4cb9bdfca2cf6a931cec44fa25ee76c502d Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 2 Aug 2011 10:24:23 -0400 Subject: abstraction of xml deserialization --- nova/api/openstack/create_instance_helper.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index ea5d4af73..b0f422655 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -311,18 +311,15 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): return {'body': {action_name: action_data}} def _action_create_image(self, node): - data = {} - value = node.getAttribute('name') - if value: - data['name'] = value - metadata_node = self.find_first_child_named(node, 'metadata') - data['metadata'] = self.extract_metadata(metadata_node) - return data + return self._deserialize_image_action(node, ('name',)) def _action_create_backup(self, node): + attributes = ('name', 'backup_type', 'rotation') + return self._deserialize_image_action(node, attributes) + + def _deserialize_image_action(self, node, allowed_attribtues): data = {} - attributes = ['name', 'backup_type', 'rotation'] - for attribute in attributes: + for attribute in allowed_attributes: value = node.getAttribute(attribute) if value: data[attribute] = value -- cgit From 695afaffaa4de359b306280c252f8f40a3bab5a7 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 2 Aug 2011 11:48:01 -0400 Subject: cleanup --- nova/api/openstack/server_metadata.py | 2 +- nova/api/openstack/servers.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index f25d36535..b0b014f86 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -53,7 +53,7 @@ class Controller(object): metadata = body['metadata'] except (KeyError, TypeError): msg = _("Malformed request body") - raise exc.HTTPBadRequest(esplanation=msg) + raise exc.HTTPBadRequest(explanation=msg) context = req.environ['nova.context'] diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1453424ce..30169d450 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -19,7 +19,6 @@ import traceback from webob import exc from xml.dom import minidom import webob -from xml.dom import minidom from nova import compute from nova import exception -- cgit From b65c7e2378d8344d1948fe4cf0dde66ef34b7204 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 2 Aug 2011 13:32:14 -0400 Subject: fixing typo --- nova/api/openstack/create_instance_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index b0f422655..53e814cd5 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -317,7 +317,7 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): attributes = ('name', 'backup_type', 'rotation') return self._deserialize_image_action(node, attributes) - def _deserialize_image_action(self, node, allowed_attribtues): + def _deserialize_image_action(self, node, allowed_attributes): data = {} for attribute in allowed_attributes: value = node.getAttribute(attribute) -- cgit From 1c2bb5bf81e384a2e833dfb172cdaf72b6ecdbae Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Tue, 2 Aug 2011 13:56:58 -0500 Subject: Changed migration to be an admin only method and updated the tests --- nova/api/openstack/__init__.py | 1 + nova/api/openstack/servers.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 868b98a31..05baa0721 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -96,6 +96,7 @@ class APIRouter(base_wsgi.Router): server_members['suspend'] = 'POST' server_members['resume'] = 'POST' server_members['rescue'] = 'POST' + server_members['migrate'] = 'POST' server_members['unrescue'] = 'POST' server_members['reset_network'] = 'POST' server_members['inject_network_info'] = 'POST' diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8d2ccc2ad..78e650c88 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -163,8 +163,7 @@ class Controller(object): 'resize': self._action_resize, 'confirmResize': self._action_confirm_resize, 'revertResize': self._action_revert_resize, - 'rebuild': self._action_rebuild, - 'migrate': self._action_migrate} + 'rebuild': self._action_rebuild,} for key in actions.keys(): if key in body: @@ -208,14 +207,6 @@ class Controller(object): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) - def _action_migrate(self, input_dict, req, id): - try: - self.compute_api.resize(req.environ['nova.context'], id) - except Exception, e: - LOG.exception(_("Error in migrate %s"), e) - raise exc.HTTPBadRequest() - return webob.Response(status_int=202) - @scheduler_api.redirect_handler def lock(self, req, id): """ @@ -341,6 +332,15 @@ class Controller(object): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) + @scheduler_api.redirect_handler + def migrate(self, req, id): + try: + self.compute_api.resize(req.environ['nova.context'], id) + except Exception, e: + LOG.exception(_("Error in migrate %s"), e) + raise exc.HTTPBadRequest() + return webob.Response(status_int=202) + @scheduler_api.redirect_handler def rescue(self, req, id): """Permit users to rescue the server.""" -- cgit From 0d8942fc5f47a5f434115ac0c1444b6485c6ba1f Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Tue, 2 Aug 2011 20:23:12 +0000 Subject: Bad merge res --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 070b89a60..002b47edb 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -163,7 +163,7 @@ class Controller(object): 'resize': self._action_resize, 'confirmResize': self._action_confirm_resize, 'revertResize': self._action_revert_resize, - 'rebuild': self._action_rebuild,} + 'rebuild': self._action_rebuild, 'createImage': self._action_create_image, } -- cgit From 8c77b6afa20c443916dd71572f22b52a5ecc88e9 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Wed, 3 Aug 2011 08:54:00 -0500 Subject: fumigate non-pep8 code --- nova/api/openstack/create_instance_helper.py | 2 +- nova/api/openstack/versions.py | 16 ++++++++-------- nova/api/openstack/views/versions.py | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index a2d18d37e..333994fcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -92,7 +92,7 @@ class CreateInstanceHelper(object): image_href = self.controller._image_ref_from_req_data(body) # If the image href was generated by nova api, strip image_href # down to an id and use the default glance connection params - + if str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 3ef72b7f6..e2f892fb6 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -34,23 +34,23 @@ VERSIONS = { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/cs-devguide-20110125.pdf" + "servers/api/v1.0/cs-devguide-20110125.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.0/application.wadl" + "servers/api/v1.0/application.wadl", }, ], "media-types": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.0+xml" + "type": "application/vnd.openstack.compute-v1.0+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.0+json" + "type": "application/vnd.openstack.compute-v1.0+json", } ], }, @@ -63,23 +63,23 @@ VERSIONS = { "rel": "describedby", "type": "application/pdf", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/cs-devguide-20110125.pdf" + "servers/api/v1.1/cs-devguide-20110125.pdf", }, { "rel": "describedby", "type": "application/vnd.sun.wadl+xml", "href": "http://docs.rackspacecloud.com/" - "servers/api/v1.1/application.wadl" + "servers/api/v1.1/application.wadl", }, ], "media-types": [ { "base": "application/xml", - "type": "application/vnd.openstack.compute-v1.1+xml" + "type": "application/vnd.openstack.compute-v1.1+xml", }, { "base": "application/json", - "type": "application/vnd.openstack.compute-v1.1+json" + "type": "application/vnd.openstack.compute-v1.1+json", } ], }, diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 547289034..03da80818 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -42,10 +42,10 @@ class ViewBuilder(object): "links": [ { "rel": "self", - "href": self.generate_href(version['id'], req.path) - } + "href": self.generate_href(version['id'], req.path), + }, ], - "media-types": version['media-types'] + "media-types": version['media-types'], }) return dict(choices=version_objs) -- cgit From 5027838724a5adfd0e15c4a10be723f03b6bbbae Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Wed, 3 Aug 2011 11:48:50 -0500 Subject: Removes extraneous body argument from server controller methods --- nova/api/openstack/create_instance_helper.py | 2 +- nova/api/openstack/servers.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index a2d18d37e..333994fcc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -92,7 +92,7 @@ class CreateInstanceHelper(object): image_href = self.controller._image_ref_from_req_data(body) # If the image href was generated by nova api, strip image_href # down to an id and use the default glance connection params - + if str(image_href).startswith(req.application_url): image_href = image_href.split('/').pop() try: diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 002b47edb..d17714371 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -328,7 +328,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def reset_network(self, req, id, body): + def reset_network(self, req, id): """ Reset networking on an instance (admin only). @@ -343,7 +343,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def inject_network_info(self, req, id, body): + def inject_network_info(self, req, id): """ Inject network info for an instance (admin only). @@ -358,7 +358,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def pause(self, req, id, body): + def pause(self, req, id): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] try: @@ -370,7 +370,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def unpause(self, req, id, body): + def unpause(self, req, id): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] try: @@ -382,7 +382,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def suspend(self, req, id, body): + def suspend(self, req, id): """permit admins to suspend the server""" context = req.environ['nova.context'] try: @@ -394,7 +394,7 @@ class Controller(object): return webob.Response(status_int=202) @scheduler_api.redirect_handler - def resume(self, req, id, body): + def resume(self, req, id): """permit admins to resume the server from suspend""" context = req.environ['nova.context'] try: -- cgit From f34a6fb9efd8d950555431f5e7268dbde8ae78c8 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 19:17:08 -0400 Subject: Remove instances of the "diaper pattern" Anywhere "except:" occurs, I tried to replace it with an explicit except on known error types. If none were known, Except was used. In the process I've been able to unearth a few evasive bugs and clean up some adjacent code. --- nova/api/direct.py | 5 ++--- nova/api/ec2/__init__.py | 4 ++-- nova/api/ec2/apirequest.py | 2 +- nova/api/openstack/common.py | 27 +++++++++++---------------- nova/api/openstack/servers.py | 22 +++++++++++----------- 5 files changed, 27 insertions(+), 33 deletions(-) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index 993815fc7..139c46d63 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -48,7 +48,6 @@ import nova.api.openstack.wsgi # Global storage for registering modules. ROUTES = {} - def register_service(path, handle): """Register a service handle at a given path. @@ -296,8 +295,8 @@ class ServiceWrapper(object): 'application/json': nova.api.openstack.wsgi.JSONDictSerializer(), }[content_type] return serializer.serialize(result) - except: - raise exception.Error("returned non-serializable type: %s" + except Exception, e: + raise exception.Error(_("Returned non-serializable type: %s") % result) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index af232edda..804e54ef9 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -147,7 +147,7 @@ class Authenticate(wsgi.Middleware): try: signature = req.params['Signature'] access = req.params['AWSAccessKeyId'] - except: + except KeyError, e: raise webob.exc.HTTPBadRequest() # Make a copy of args for authentication and signature verification. @@ -211,7 +211,7 @@ class Requestify(wsgi.Middleware): for non_arg in non_args: # Remove, but raise KeyError if omitted args.pop(non_arg) - except: + except KeyError, e: raise webob.exc.HTTPBadRequest() LOG.debug(_('action: %s'), action) diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index 7d78c5cfa..9a3e55925 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -104,7 +104,7 @@ class APIRequest(object): for key in data.keys(): val = data[key] el.appendChild(self._render_data(xml, key, val)) - except: + except Exception: LOG.debug(data) raise diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index efa4ab385..1304379a8 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -16,7 +16,7 @@ # under the License. import re -from urlparse import urlparse +import urlparse from xml.dom import minidom import webob @@ -137,8 +137,8 @@ def get_id_from_href(href): if re.match(r'\d+$', str(href)): return int(href) try: - return int(urlparse(href).path.split('/')[-1]) - except: + return int(urlparse.urlsplit(href).path.split('/')[-1]) + except ValueError, e: LOG.debug(_("Error extracting id from href: %s") % href) raise ValueError(_('could not parse id from href')) @@ -153,22 +153,17 @@ def remove_version_from_href(href): Returns: 'http://www.nova.com' """ - try: - #removes the first instance that matches /v#.#/ - new_href = re.sub(r'[/][v][0-9]+\.[0-9]+[/]', '/', href, count=1) + parsed_url = urlparse.urlsplit(href) + new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, count=1) - #if no version was found, try finding /v#.# at the end of the string - if new_href == href: - new_href = re.sub(r'[/][v][0-9]+\.[0-9]+$', '', href, count=1) - except: - LOG.debug(_("Error removing version from href: %s") % href) - msg = _('could not parse version from href') + if new_path == parsed_url.path: + msg = _('href %s does not contain version') % href + LOG.debug(msg) raise ValueError(msg) - if new_href == href: - msg = _('href does not contain version') - raise ValueError(msg) - return new_href + parsed_url = list(parsed_url) + parsed_url[2] = new_path + return urlparse.urlunsplit(parsed_url) def get_version_from_href(href): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 002b47edb..493845e8c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -290,7 +290,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.lock(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::lock %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -306,7 +306,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.unlock(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::unlock %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -321,7 +321,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.get_lock(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::get_lock %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -336,7 +336,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.reset_network(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::reset_network %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -351,7 +351,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.inject_network_info(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::inject_network_info %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -363,7 +363,7 @@ class Controller(object): ctxt = req.environ['nova.context'] try: self.compute_api.pause(ctxt, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::pause %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -375,7 +375,7 @@ class Controller(object): ctxt = req.environ['nova.context'] try: self.compute_api.unpause(ctxt, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("Compute.api::unpause %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -387,7 +387,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.suspend(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::suspend %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -399,7 +399,7 @@ class Controller(object): context = req.environ['nova.context'] try: self.compute_api.resume(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::resume %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -420,7 +420,7 @@ class Controller(object): context = req.environ["nova.context"] try: self.compute_api.rescue(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::rescue %s"), readable) raise exc.HTTPUnprocessableEntity() @@ -432,7 +432,7 @@ class Controller(object): context = req.environ["nova.context"] try: self.compute_api.unrescue(context, id) - except: + except Exception: readable = traceback.format_exc() LOG.exception(_("compute.api::unrescue %s"), readable) raise exc.HTTPUnprocessableEntity() -- cgit From 4eed25b0f01b510d0d90e864eef7f285964ab293 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 4 Aug 2011 11:11:12 -0400 Subject: The OSAPI v1.0 image create POST request should store the instance_id as a Glance property. --- 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 0834adfa5..c76738d30 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -116,13 +116,17 @@ class ControllerV10(Controller): try: image_name = image["name"] - server_id = image["serverId"] + instance_id = image["serverId"] except KeyError as missing_key: msg = _("Image entity requires %s") % missing_key raise webob.exc.HTTPBadRequest(explanation=msg) context = req.environ["nova.context"] - image = self._compute_service.snapshot(context, server_id, image_name) + props = {'instance_id': instance_id} + image = self._compute_service.snapshot(context, + instance_id, + image_name, + extra_properties=props) return dict(image=self.get_builder(req).build(image, detail=True)) -- cgit From 5826a793e7e05db8ccc14d15326245246fb652d4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 4 Aug 2011 12:48:13 -0400 Subject: fixing typo --- nova/api/openstack/create_instance_helper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 178685889..2a8e7fd7e 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -332,7 +332,8 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): if value: data[attribute] = value metadata_node = self.find_first_child_named(node, 'metadata') - data['metadata'] = self.extract_metadata(metadata_node) + metadata = self.metadata_deserializer.extract_metadata(metadata_node) + data['metadata'] = metadata return data def create(self, string): -- cgit From f22c19c6f78451074c33fe8da855755574cb6b49 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 15:51:41 -0400 Subject: Split serverXMLDeserializers into v1.0 and v1.1 --- nova/api/openstack/create_instance_helper.py | 48 ++++++++++++++++++++++++++++ nova/api/openstack/servers.py | 7 +++- 2 files changed, 54 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2a8e7fd7e..832c890b6 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -304,6 +304,54 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): metadata_deserializer = common.MetadataXMLDeserializer() + def create(self, string): + """Deserialize an xml-formatted server create request""" + dom = minidom.parseString(string) + server = self._extract_server(dom) + return {'body': {'server': server}} + + def _extract_server(self, node): + """Marshal the server attribute of a parsed request""" + server = {} + server_node = self.find_first_child_named(node, 'server') + + attributes = ["name", "imageId", "flavorId", "adminPass"] + for attr in attributes: + if server_node.getAttribute(attr): + server[attr] = server_node.getAttribute(attr) + + metadata_node = self.find_first_child_named(server_node, "metadata") + server["metadata"] = self.metadata_deserializer.extract_metadata( + metadata_node) + + server["personality"] = self._extract_personality(server_node) + + return server + + def _extract_personality(self, server_node): + """Marshal the personality attribute of a parsed request""" + node = self.find_first_child_named(server_node, "personality") + personality = [] + if node is not None: + for file_node in self.find_children_named(node, "file"): + item = {} + if file_node.hasAttribute("path"): + item["path"] = file_node.getAttribute("path") + item["contents"] = self.extract_text(file_node) + personality.append(item) + return personality + + +class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): + """ + Deserializer to handle xml-formatted server create requests. + + Handles standard server attributes as well as optional metadata + and personality attributes + """ + + metadata_deserializer = common.MetadataXMLDeserializer() + def action(self, string): dom = minidom.parseString(string) action_node = dom.childNodes[0] diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index d17714371..3acc4510e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -891,8 +891,13 @@ def create_resource(version='1.0'): 'application/xml': xml_serializer, } + xml_deserializer = { + '1.0': helper.ServerXMLDeserializer(), + '1.1': helper.ServerXMLDeserializerV11(), + }[version] + body_deserializers = { - 'application/xml': helper.ServerXMLDeserializer(), + 'application/xml': xml_deserializer, } serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) -- cgit From 9ce80fc74b3ea4513369b795d1e6891d6dfa8e03 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 16:20:37 -0400 Subject: Updated create image server action to respect 1.1 --- nova/api/openstack/create_instance_helper.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 832c890b6..eec861428 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -380,8 +380,10 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): if value: data[attribute] = value metadata_node = self.find_first_child_named(node, 'metadata') - metadata = self.metadata_deserializer.extract_metadata(metadata_node) - data['metadata'] = metadata + if metadata_node is not None: + metadata = self.metadata_deserializer.extract_metadata( + metadata_node) + data['metadata'] = metadata return data def create(self, string): @@ -395,29 +397,32 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): server = {} server_node = self.find_first_child_named(node, 'server') - attributes = ["name", "imageId", "flavorId", "imageRef", - "flavorRef", "adminPass"] + attributes = ["name", "imageRef", "flavorRef", "adminPass"] for attr in attributes: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) metadata_node = self.find_first_child_named(server_node, "metadata") - server["metadata"] = self.metadata_deserializer.extract_metadata( - metadata_node) + if metadata_node is not None: + server["metadata"] = self.extract_metadata(metadata_node) - server["personality"] = self._extract_personality(server_node) + personality = self._extract_personality(server_node) + if personality is not None: + server["personality"] = personality return server def _extract_personality(self, server_node): """Marshal the personality attribute of a parsed request""" node = self.find_first_child_named(server_node, "personality") - personality = [] if node is not None: + personality = [] for file_node in self.find_children_named(node, "file"): item = {} if file_node.hasAttribute("path"): item["path"] = file_node.getAttribute("path") item["contents"] = self.extract_text(file_node) personality.append(item) - return personality + return personality + else: + return None -- cgit From 0f515d6d31b2c95ed7f1e3ca8d9d67f98fda9fbe Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 16:26:31 -0400 Subject: Added XML serialization for server actions --- nova/api/openstack/create_instance_helper.py | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index eec861428..894d47beb 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -360,6 +360,12 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): action_deserializer = { 'createImage': self._action_create_image, 'createBackup': self._action_create_backup, + 'changePassword': self._action_change_password, + 'reboot': self._action_reboot, + 'rebuild': self._action_rebuild, + 'resize': self._action_resize, + 'confirmResize': self._action_confirm_resize, + 'revertResize': self._action_revert_resize, }.get(action_name, self.default) action_data = action_deserializer(action_node) @@ -373,6 +379,46 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): attributes = ('name', 'backup_type', 'rotation') return self._deserialize_image_action(node, attributes) + def _action_change_password(self, node): + if not node.hasAttribute("adminPass"): + raise AttributeError("No adminPass was specified in request") + return {"adminPass": node.getAttribute("adminPass")} + + def _action_reboot(self, node): + if not node.hasAttribute("type"): + raise AttributeError("No reboot type was specified in request") + return {"type": node.getAttribute("type")} + + def _action_rebuild(self, node): + rebuild = {} + if node.hasAttribute("name"): + rebuild['name'] = node.getAttribute("name") + + metadata_node = self.find_first_child_named(node, "metadata") + if metadata_node is not None: + rebuild["metadata"] = self.extract_metadata(metadata_node) + + personality = self._extract_personality(node) + if personality is not None: + rebuild["personality"] = personality + + if not node.hasAttribute("imageRef"): + raise AttributeError("No imageRef was specified in request") + rebuild["imageRef"] = node.getAttribute("imageRef") + + return rebuild + + def _action_resize(self, node): + if not node.hasAttribute("flavorRef"): + raise AttributeError("No flavorRef was specified in request") + return {"flavorRef": node.getAttribute("flavorRef")} + + def _action_confirm_resize(self, node): + return None + + def _action_revert_resize(self, node): + return None + def _deserialize_image_action(self, node, allowed_attributes): data = {} for attribute in allowed_attributes: -- cgit From 5b463f5d14c62f66250d5edc3edbd2ded704e0da Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 4 Aug 2011 16:38:55 -0400 Subject: Added missing tests for server actions Updated reboot to verify the reboot type is HARD or SOFT Fixed case of having an empty flavorref on resize --- nova/api/openstack/servers.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3acc4510e..fa4d11e24 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -268,9 +268,13 @@ class Controller(object): def _action_reboot(self, input_dict, req, id): if 'reboot' in input_dict and 'type' in input_dict['reboot']: reboot_type = input_dict['reboot']['type'] + if (not reboot_type == 'HARD') and (not reboot_type == 'SOFT'): + msg = _("Argument 'type' for reboot is not HARD or SOFT") + LOG.exception(msg) + raise exc.HTTPBadRequest() else: LOG.exception(_("Missing argument 'type' for reboot")) - raise exc.HTTPUnprocessableEntity() + raise exc.HTTPBadRequest() try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver @@ -646,6 +650,9 @@ class ControllerV11(Controller): """ Resizes a given instance to the flavor size requested """ try: flavor_ref = input_dict["resize"]["flavorRef"] + if not flavor_ref: + msg = _("Resize request has invalid 'flavorRef' attribute.") + raise exc.HTTPBadRequest(explanation=msg) except (KeyError, TypeError): msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) -- cgit From 637dfc0f44cbd5bf0c76d80d708a241e562403ac Mon Sep 17 00:00:00 2001 From: Gabe Westmaas Date: Fri, 5 Aug 2011 01:55:53 +0000 Subject: Added explanations to exceptions and cleaned up reboot types --- nova/api/openstack/servers.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fa4d11e24..9a55af8ea 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -267,14 +267,16 @@ class Controller(object): def _action_reboot(self, input_dict, req, id): if 'reboot' in input_dict and 'type' in input_dict['reboot']: - reboot_type = input_dict['reboot']['type'] - if (not reboot_type == 'HARD') and (not reboot_type == 'SOFT'): + valid_reboot_types = ['HARD', 'SOFT'] + reboot_type = input_dict['reboot']['type'].upper() + if not valid_reboot_types.count(reboot_type): msg = _("Argument 'type' for reboot is not HARD or SOFT") LOG.exception(msg) - raise exc.HTTPBadRequest() + raise exc.HTTPBadRequest(explanation=msg) else: - LOG.exception(_("Missing argument 'type' for reboot")) - raise exc.HTTPBadRequest() + msg = _("Missing argument 'type' for reboot") + LOG.exception(msg) + raise exc.HTTPBadRequest(explanation=msg) try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver -- cgit From 9602a558b6be6e6812626b986c0f9557a3862fe6 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 5 Aug 2011 11:59:14 -0400 Subject: glance image service pagination --- 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 c76738d30..b9bc83fde 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -143,7 +143,7 @@ class ControllerV10(Controller): """ context = req.environ['nova.context'] filters = self._get_filters(req) - images = self._image_service.index(context, filters) + images = self._image_service.index(context, filters=filters) images = common.limited(images, req) builder = self.get_builder(req).build return dict(images=[builder(image, detail=False) for image in images]) @@ -156,7 +156,7 @@ class ControllerV10(Controller): """ context = req.environ['nova.context'] filters = self._get_filters(req) - images = self._image_service.detail(context, filters) + images = self._image_service.detail(context, filters=filters) images = common.limited(images, req) builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) -- cgit From f03c926a7d28ee35789048ea53c36cd452ed3571 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 5 Aug 2011 14:28:22 -0500 Subject: Allow actions queries by UUID and PEP8 fixes. --- nova/api/direct.py | 1 + nova/api/openstack/common.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index 139c46d63..fdd2943d2 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -48,6 +48,7 @@ import nova.api.openstack.wsgi # Global storage for registering modules. ROUTES = {} + def register_service(path, handle): """Register a service handle at a given path. diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 715b9e4a4..4548c2c75 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -154,7 +154,8 @@ def remove_version_from_href(href): """ parsed_url = urlparse.urlsplit(href) - new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, count=1) + new_path = re.sub(r'^/v[0-9]+\.[0-9]+(/|$)', r'\1', parsed_url.path, + count=1) if new_path == parsed_url.path: msg = _('href %s does not contain version') % href -- cgit