diff options
| author | Rick Harris <rconradharris@gmail.com> | 2012-01-17 08:18:36 +0000 |
|---|---|---|
| committer | Rick Harris <rconradharris@gmail.com> | 2012-01-17 15:00:08 -0600 |
| commit | 1357caea7955a558e309acbfff304eea73b16893 (patch) | |
| tree | c8f23c17be59276f40421b4e55b0d901d53a2bfa | |
| parent | 8c1e0022028a454f536ff211aa200cab9b0111aa (diff) | |
| download | nova-1357caea7955a558e309acbfff304eea73b16893.tar.gz nova-1357caea7955a558e309acbfff304eea73b16893.tar.xz nova-1357caea7955a558e309acbfff304eea73b16893.zip | |
Rebuild/Resize support for disk-config.
Fixes bug 917306
Change-Id: I27848a4503a5027b0e5f9cad3f110ea67c462327
| -rw-r--r-- | nova/api/openstack/compute/contrib/disk_config.py | 250 | ||||
| -rw-r--r-- | nova/api/openstack/compute/servers.py | 26 | ||||
| -rw-r--r-- | nova/compute/api.py | 5 |
3 files changed, 141 insertions, 140 deletions
diff --git a/nova/api/openstack/compute/contrib/disk_config.py b/nova/api/openstack/compute/contrib/disk_config.py index 392291652..30aac0acc 100644 --- a/nova/api/openstack/compute/contrib/disk_config.py +++ b/nova/api/openstack/compute/contrib/disk_config.py @@ -16,42 +16,38 @@ """Disk Config extension.""" -from xml.dom import minidom - from webob import exc from nova.api.openstack import extensions +from nova.api.openstack import wsgi from nova.api.openstack import xmlutil -from nova import compute from nova import db -from nova import log as logging from nova import utils -LOG = logging.getLogger('nova.api.openstack.contrib.disk_config') - ALIAS = 'RAX-DCF' XMLNS_DCF = "http://docs.rackspacecloud.com/servers/api/ext/diskConfig/v1.0" +API_DISK_CONFIG = "%s:diskConfig" % ALIAS +INTERNAL_DISK_CONFIG = "auto_disk_config" -class ServerDiskConfigTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('server') - root.set('{%s}diskConfig' % XMLNS_DCF, '%s:diskConfig' % ALIAS) - return xmlutil.SlaveTemplate(root, 1, nsmap={ALIAS: XMLNS_DCF}) +def disk_config_to_api(value): + return 'AUTO' if value else 'MANUAL' -class ServersDiskConfigTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('servers') - elem = xmlutil.SubTemplateElement(root, 'server', selector='servers') - elem.set('{%s}diskConfig' % XMLNS_DCF, '%s:diskConfig' % ALIAS) - return xmlutil.SlaveTemplate(root, 1, nsmap={ALIAS: XMLNS_DCF}) +def disk_config_from_api(value): + if value == 'AUTO': + return True + elif value == 'MANUAL': + return False + else: + msg = _("%s must be either 'MANUAL' or 'AUTO'." % API_DISK_CONFIG) + raise exc.HTTPBadRequest(explanation=msg) class ImageDiskConfigTemplate(xmlutil.TemplateBuilder): def construct(self): root = xmlutil.TemplateElement('image') - root.set('{%s}diskConfig' % XMLNS_DCF, '%s:diskConfig' % ALIAS) + root.set('{%s}diskConfig' % XMLNS_DCF, API_DISK_CONFIG) return xmlutil.SlaveTemplate(root, 1, nsmap={ALIAS: XMLNS_DCF}) @@ -59,142 +55,130 @@ class ImagesDiskConfigTemplate(xmlutil.TemplateBuilder): def construct(self): root = xmlutil.TemplateElement('images') elem = xmlutil.SubTemplateElement(root, 'image', selector='images') - elem.set('{%s}diskConfig' % XMLNS_DCF, '%s:diskConfig' % ALIAS) + elem.set('{%s}diskConfig' % XMLNS_DCF, API_DISK_CONFIG) return xmlutil.SlaveTemplate(root, 1, nsmap={ALIAS: XMLNS_DCF}) -def disk_config_to_api(value): - return 'AUTO' if value else 'MANUAL' - - -def disk_config_from_api(value): - if value == 'AUTO': - return True - elif value == 'MANUAL': - return False - else: - msg = _("RAX-DCF:diskConfig must be either 'MANUAL' or 'AUTO'.") - raise exc.HTTPBadRequest(explanation=msg) - - -class Disk_config(extensions.ExtensionDescriptor): - """Disk Management Extension""" - - name = "DiskConfig" - alias = ALIAS - namespace = XMLNS_DCF - updated = "2011-09-27:00:00+00:00" +class ImageDiskConfigController(wsgi.Controller): + def _add_disk_config(self, context, images): + for image in images: + metadata = image['metadata'] + if INTERNAL_DISK_CONFIG in metadata: + raw_value = metadata[INTERNAL_DISK_CONFIG] + value = utils.bool_from_str(raw_value) + image[API_DISK_CONFIG] = disk_config_to_api(value) - API_DISK_CONFIG = "%s:diskConfig" % ALIAS - INTERNAL_DISK_CONFIG = "auto_disk_config" + @wsgi.extends + def show(self, req, resp_obj, id): + if 'image' in resp_obj.obj: + context = req.environ['nova.context'] + resp_obj.attach(xml=ImageDiskConfigTemplate()) + image = resp_obj.obj['image'] + self._add_disk_config(context, [image]) - def __init__(self, ext_mgr): - super(Disk_config, self).__init__(ext_mgr) - self.compute_api = compute.API() + @wsgi.extends + def detail(self, req, resp_obj): + if 'images' in resp_obj.obj: + context = req.environ['nova.context'] + resp_obj.attach(xml=ImagesDiskConfigTemplate()) + images = resp_obj.obj['images'] + self._add_disk_config(context, images) - def _extract_resource_from_body(self, res, body, - singular, singular_template, plural, plural_template): - """Returns a list of the given resources from the request body. - The templates passed in are used for XML serialization. - """ - template = res.environ.get('nova.template') - if plural in body: - resources = body[plural] - if template: - template.attach(plural_template) - elif singular in body: - resources = [body[singular]] - if template: - template.attach(singular_template) - else: - resources = [] +class ServerDiskConfigTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('server') + root.set('{%s}diskConfig' % XMLNS_DCF, API_DISK_CONFIG) + return xmlutil.SlaveTemplate(root, 1, nsmap={ALIAS: XMLNS_DCF}) - return resources - def _GET_servers(self, req, res, body): - context = req.environ['nova.context'] +class ServersDiskConfigTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('servers') + elem = xmlutil.SubTemplateElement(root, 'server', selector='servers') + elem.set('{%s}diskConfig' % XMLNS_DCF, API_DISK_CONFIG) + return xmlutil.SlaveTemplate(root, 1, nsmap={ALIAS: XMLNS_DCF}) - servers = self._extract_resource_from_body(res, body, - singular='server', singular_template=ServerDiskConfigTemplate(), - plural='servers', plural_template=ServersDiskConfigTemplate()) +class ServerDiskConfigController(wsgi.Controller): + def _add_disk_config(self, context, servers): # Filter out any servers that already have the key set (most likely # from a remote zone) - servers = filter(lambda s: self.API_DISK_CONFIG not in s, servers) + servers = [s for s in servers if API_DISK_CONFIG not in s] # Get DB information for servers uuids = [server['id'] for server in servers] db_servers = db.instance_get_all_by_filters(context, {'uuid': uuids}) - db_servers = dict([(s['uuid'], s) for s in db_servers]) + db_servers_by_uuid = dict((s['uuid'], s) for s in db_servers) for server in servers: - db_server = db_servers.get(server['id']) + db_server = db_servers_by_uuid.get(server['id']) if db_server: - value = db_server[self.INTERNAL_DISK_CONFIG] - server[self.API_DISK_CONFIG] = disk_config_to_api(value) + value = db_server[INTERNAL_DISK_CONFIG] + server[API_DISK_CONFIG] = disk_config_to_api(value) + + def _show(self, req, resp_obj): + if 'server' in resp_obj.obj: + context = req.environ['nova.context'] + resp_obj.attach(xml=ServerDiskConfigTemplate()) + server = resp_obj.obj['server'] + self._add_disk_config(context, [server]) + + @wsgi.extends + def show(self, req, resp_obj, id): + self._show(req, resp_obj) + + @wsgi.extends + def detail(self, req, resp_obj): + if 'servers' in resp_obj.obj: + context = req.environ['nova.context'] + resp_obj.attach(xml=ServersDiskConfigTemplate()) + servers = resp_obj.obj['servers'] + self._add_disk_config(context, servers) + + def _set_disk_config(self, dict_): + if API_DISK_CONFIG in dict_: + api_value = dict_[API_DISK_CONFIG] + internal_value = disk_config_from_api(api_value) + dict_[INTERNAL_DISK_CONFIG] = internal_value + + @wsgi.extends + def create(self, req, body): + self._set_disk_config(body['server']) + resp_obj = (yield) + self._show(req, resp_obj) + + @wsgi.extends + def update(self, req, id, body): + self._set_disk_config(body['server']) + resp_obj = (yield) + self._show(req, resp_obj) + + @wsgi.extends(action='rebuild') + def _action_rebuild(self, req, id, body): + self._set_disk_config(body['rebuild']) + resp_obj = (yield) + self._show(req, resp_obj) + + @wsgi.extends(action='resize') + def _action_resize(self, req, id, body): + self._set_disk_config(body['resize']) + yield - return res - def _GET_images(self, req, res, body): - images = self._extract_resource_from_body(res, body, - singular='image', singular_template=ImageDiskConfigTemplate(), - plural='images', plural_template=ImagesDiskConfigTemplate()) +class Disk_config(extensions.ExtensionDescriptor): + """Disk Management Extension""" - for image in images: - metadata = image['metadata'] + name = "DiskConfig" + alias = ALIAS + namespace = XMLNS_DCF + updated = "2011-09-27:00:00+00:00" - if self.INTERNAL_DISK_CONFIG in metadata: - raw_value = metadata[self.INTERNAL_DISK_CONFIG] - value = utils.bool_from_str(raw_value) - image[self.API_DISK_CONFIG] = disk_config_to_api(value) - - return res - - def _POST_servers(self, req, res, body): - return self._GET_servers(req, res, body) - - def _pre_POST_servers(self, req): - # NOTE(sirp): deserialization currently occurs *after* pre-processing - # extensions are called. Until extensions are refactored so that - # deserialization occurs earlier, we have to perform the - # deserialization ourselves. - content_type = req.content_type - - if 'xml' in content_type: - node = minidom.parseString(req.body) - server = node.getElementsByTagName('server')[0] - api_value = server.getAttribute(self.API_DISK_CONFIG) - if api_value: - value = disk_config_from_api(api_value) - server.setAttribute(self.INTERNAL_DISK_CONFIG, str(value)) - req.body = str(node.toxml()) - else: - body = utils.loads(req.body) - server = body['server'] - api_value = server.get(self.API_DISK_CONFIG) - if api_value: - value = disk_config_from_api(api_value) - server[self.INTERNAL_DISK_CONFIG] = value - req.body = utils.dumps(body) - - def _pre_PUT_servers(self, req): - return self._pre_POST_servers(req) - - def get_request_extensions(self): - ReqExt = extensions.RequestExtension - return [ - ReqExt(method='GET', - url_route='/:(project_id)/servers/:(id)', - handler=self._GET_servers), - ReqExt(method='POST', - url_route='/:(project_id)/servers', - handler=self._POST_servers, - pre_handler=self._pre_POST_servers), - ReqExt(method='PUT', - url_route='/:(project_id)/servers/:(id)', - pre_handler=self._pre_PUT_servers), - ReqExt(method='GET', - url_route='/:(project_id)/images/:(id)', - handler=self._GET_images) - ] + def get_controller_extensions(self): + servers_extension = extensions.ControllerExtension( + self, 'servers', ServerDiskConfigController()) + + images_extension = extensions.ControllerExtension( + self, 'images', ImageDiskConfigController()) + + return [servers_extension, images_extension] diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index 274589696..0648a43ba 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -277,6 +277,9 @@ class ActionDeserializer(CommonDeserializer): if node.hasAttribute("name"): rebuild['name'] = node.getAttribute("name") + if node.hasAttribute("auto_disk_config"): + rebuild['auto_disk_config'] = node.getAttribute("auto_disk_config") + metadata_node = self.find_first_child_named(node, "metadata") if metadata_node is not None: rebuild["metadata"] = self.extract_metadata(metadata_node) @@ -292,9 +295,17 @@ class ActionDeserializer(CommonDeserializer): return rebuild def _action_resize(self, node): - if not node.hasAttribute("flavorRef"): + resize = {} + + if node.hasAttribute("flavorRef"): + resize["flavorRef"] = node.getAttribute("flavorRef") + else: raise AttributeError("No flavorRef was specified in request") - return {"flavorRef": node.getAttribute("flavorRef")} + + if node.hasAttribute("auto_disk_config"): + rezise['auto_disk_config'] = node.getAttribute("auto_disk_config") + + return resize def _action_confirm_resize(self, node): return None @@ -891,13 +902,13 @@ class Controller(wsgi.Controller): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) - def _resize(self, req, instance_id, flavor_id): + def _resize(self, req, instance_id, flavor_id, **kwargs): """Begin the resize process with given instance/flavor.""" context = req.environ["nova.context"] instance = self._get_server(context, instance_id) try: - self.compute_api.resize(context, instance, flavor_id) + self.compute_api.resize(context, instance, flavor_id, **kwargs) except exception.FlavorNotFound: msg = _("Unable to locate requested flavor.") raise exc.HTTPBadRequest(explanation=msg) @@ -996,7 +1007,11 @@ class Controller(wsgi.Controller): msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) - return self._resize(req, id, flavor_ref) + kwargs = {} + if 'auto_disk_config' in body['resize']: + kwargs['auto_disk_config'] = body['resize']['auto_disk_config'] + + return self._resize(req, id, flavor_ref, **kwargs) @wsgi.response(202) @wsgi.serializers(xml=FullServerTemplate) @@ -1031,6 +1046,7 @@ class Controller(wsgi.Controller): 'accessIPv4': 'access_ip_v4', 'accessIPv6': 'access_ip_v6', 'metadata': 'metadata', + 'auto_disk_config': 'auto_disk_config', } if 'accessIPv4' in body: diff --git a/nova/compute/api.py b/nova/compute/api.py index a75d5c027..23bb4de82 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1346,7 +1346,7 @@ class API(base.Base): @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF], task_state=[None]) @scheduler_api.reroute_compute("resize") - def resize(self, context, instance, flavor_id=None): + def resize(self, context, instance, flavor_id=None, **kwargs): """Resize (ie, migrate) a running instance. If flavor_id is None, the process is considered a migration, keeping @@ -1379,7 +1379,8 @@ class API(base.Base): self.update(context, instance, vm_state=vm_states.RESIZING, - task_state=task_states.RESIZE_PREP) + task_state=task_states.RESIZE_PREP, + **kwargs) request_spec = { 'instance_type': new_instance_type, |
