diff options
| author | Sandy Walsh <sandy.walsh@rackspace.com> | 2011-08-08 09:12:56 -0700 |
|---|---|---|
| committer | Sandy Walsh <sandy.walsh@rackspace.com> | 2011-08-08 09:12:56 -0700 |
| commit | 7730d4d1ebda5c88ae83f1f5e6dbd6b0a5c82ee4 (patch) | |
| tree | c3c14f81db733971791a03ef8d5e6b66a634c13d /nova/api | |
| parent | 19e50320e36f02ce11a6aaae8f88a6ddbc132859 (diff) | |
| parent | ec57e2a27ebfc8eba84d82f5372408e3d85a9272 (diff) | |
| download | nova-7730d4d1ebda5c88ae83f1f5e6dbd6b0a5c82ee4.tar.gz nova-7730d4d1ebda5c88ae83f1f5e6dbd6b0a5c82ee4.tar.xz nova-7730d4d1ebda5c88ae83f1f5e6dbd6b0a5c82ee4.zip | |
another trunk merge
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/openstack/__init__.py | 3 | ||||
| -rw-r--r-- | nova/api/openstack/common.py | 13 | ||||
| -rw-r--r-- | nova/api/openstack/create_instance_helper.py | 117 | ||||
| -rw-r--r-- | nova/api/openstack/images.py | 5 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 24 |
5 files changed, 147 insertions, 15 deletions
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index d6a98c2cd..4d49df2ad 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -50,6 +50,9 @@ FLAGS = flags.FLAGS flags.DEFINE_bool('allow_admin_api', False, 'When True, this API service will accept admin operations.') +flags.DEFINE_bool('allow_instance_snapshots', + True, + 'When True, this API service will permit instance snapshot operations.') class FaultWrapper(base_wsgi.Middleware): diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 75e862630..ac2104a5f 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import functools import re import urlparse from xml.dom import minidom @@ -280,3 +281,15 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer): def default(self, *args, **kwargs): return '' + + +def check_snapshots_enabled(f): + @functools.wraps(f) + def inner(*args, **kwargs): + if not FLAGS.allow_instance_snapshots: + LOG.warn(_('Rejecting snapshot request, snapshots currently' + ' disabled')) + msg = _("Instance snapshots are not permitted at this time.") + raise webob.exc.HTTPBadRequest(explanation=msg) + return f(*args, **kwargs) + return inner diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2a8e7fd7e..894d47beb 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] @@ -312,6 +360,12 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): 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) @@ -325,6 +379,46 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): 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: @@ -332,8 +426,10 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): 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): @@ -347,29 +443,32 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): 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 diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index c76738d30..0aabb9e56 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -106,6 +106,7 @@ class Controller(object): class ControllerV10(Controller): """Version 1.0 specific controller logic.""" + @common.check_snapshots_enabled def create(self, req, body): """Snapshot a server instance and save the image.""" try: @@ -143,7 +144,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 +157,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]) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3930982dc..f1a27a98c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -240,6 +240,7 @@ class Controller(object): resp.headers['Location'] = image_ref return resp + @common.check_snapshots_enabled def _action_create_image(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -267,10 +268,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'] + 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(explanation=msg) else: - LOG.exception(_("Missing argument 'type' for reboot")) - raise exc.HTTPUnprocessableEntity() + 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 @@ -646,6 +653,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) @@ -680,6 +690,7 @@ class ControllerV11(Controller): return webob.Response(status_int=202) + @common.check_snapshots_enabled def _action_create_image(self, input_dict, req, instance_id): """Snapshot a server instance.""" entity = input_dict.get("createImage", {}) @@ -891,8 +902,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) |
