diff options
| author | Brian Waldon <brian.waldon@rackspace.com> | 2011-08-02 19:03:33 +0000 |
|---|---|---|
| committer | Tarmac <> | 2011-08-02 19:03:33 +0000 |
| commit | 65ba8bda43aa79080f6fec9c396f412c294718b8 (patch) | |
| tree | ad3a289dae895fa73734261253c08df0300be956 /nova/api | |
| parent | cf7eefab9e7891d449b115c0c50c4b76ae45743f (diff) | |
| parent | b65c7e2378d8344d1948fe4cf0dde66ef34b7204 (diff) | |
| download | nova-65ba8bda43aa79080f6fec9c396f412c294718b8.tar.gz nova-65ba8bda43aa79080f6fec9c396f412c294718b8.tar.xz nova-65ba8bda43aa79080f6fec9c396f412c294718b8.zip | |
Moves image creation from POST /images to POST /servers/<id>/action
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/openstack/create_instance_helper.py | 31 | ||||
| -rw-r--r-- | nova/api/openstack/images.py | 131 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 127 |
3 files changed, 174 insertions, 115 deletions
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 9199c193d..53e814cd5 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -296,6 +296,37 @@ 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, + 'createBackup': self._action_create_backup, + }.get(action_name, self.default) + + action_data = action_deserializer(action_node) + + return {'body': {action_name: action_data}} + + def _action_create_image(self, node): + 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_attributes): + data = {} + for attribute in allowed_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) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 9ba8b639e..0834adfa5 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -98,79 +98,34 @@ class Controller(object): self._image_service.delete(context, id) return webob.exc.HTTPNoContent() - 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") - - 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) - - 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 create(self, req, body): + """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: + 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)) + def get_builder(self, request): """Property to get the ViewBuilder class we need to use.""" base_url = request.application_url @@ -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,37 +194,8 @@ 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() class ImageXMLSerializer(wsgi.XMLDictSerializer): @@ -369,12 +288,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 8d2ccc2ad..7b757143d 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 @@ -154,23 +155,95 @@ 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""" - 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(): + 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 actions[key](body, req, id) + 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() + def _action_change_password(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -607,6 +680,48 @@ 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", {}) + + try: + image_name = entity["name"] + + except KeyError: + msg = _("createImage entity requires name attribute") + raise webob.exc.HTTPBadRequest(explanation=msg) + + 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, + '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.snapshot(context, + instance_id, + image_name, + 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 get_default_xmlns(self, req): return common.XML_NS_V11 |
