diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json index f1aa8aa680a9..fa780a5562c5 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.json +++ b/doc/api_samples/all_extensions/extensions-get-resp.json @@ -464,6 +464,14 @@ "namespace": "http://docs.openstack.org/compute/ext/networks_associate/api/v2", "updated": "2012-11-19T00:00:00+00:00" }, + { + "alias": "os-preserve-ephemeral-rebuild", + "description": "Allow preservation of the ephemeral partition on rebuild.", + "links": [], + "name": "PreserveEphemeralOnRebuild", + "namespace": "http://docs.openstack.org/compute/ext/preserve_ephemeral_rebuild/api/v2", + "updated": "2013-12-17T00:00:00+00:00" + }, { "alias": "os-quota-class-sets", "description": "Quota classes management support.", diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml index 768d4314da6e..d12e74d80a56 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.xml +++ b/doc/api_samples/all_extensions/extensions-get-resp.xml @@ -194,6 +194,9 @@ Network association support. + + Allow preservation of the ephemeral partition on rebuild. + Quota classes management support. diff --git a/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.json b/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.json new file mode 100644 index 000000000000..bd44136f0af8 --- /dev/null +++ b/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.json @@ -0,0 +1,55 @@ +{ + "server": { + "accessIPv4": "1.2.3.4", + "accessIPv6": "fe80::100", + "addresses": { + "private": [ + { + "addr": "192.168.0.3", + "version": 4 + } + ] + }, + "adminPass": "seekr3t", + "created": "2013-12-30T12:28:14Z", + "flavor": { + "id": "1", + "links": [ + { + "href": "http://openstack.example.com/openstack/flavors/1", + "rel": "bookmark" + } + ] + }, + "hostId": "ee8ea077f8548ce25c59c2d5020d0f82810c815c210fd68194a5c0f8", + "id": "810e78d5-47fe-48bf-9559-bfe5dc918685", + "image": { + "id": "70a599e0-31e7-49b7-b260-868f441e862b", + "links": [ + { + "href": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b", + "rel": "bookmark" + } + ] + }, + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/810e78d5-47fe-48bf-9559-bfe5dc918685", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/810e78d5-47fe-48bf-9559-bfe5dc918685", + "rel": "bookmark" + } + ], + "metadata": { + "meta var": "meta val" + }, + "name": "foobar", + "progress": 0, + "status": "ACTIVE", + "tenant_id": "openstack", + "updated": "2013-12-30T12:28:15Z", + "user_id": "fake" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.xml b/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.xml new file mode 100644 index 000000000000..bc18e7405b46 --- /dev/null +++ b/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.xml @@ -0,0 +1,19 @@ + + + + + + + + + + Apache1 + + + + + + + + + \ No newline at end of file diff --git a/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.json b/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.json new file mode 100644 index 000000000000..99a95cde5bcc --- /dev/null +++ b/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.json @@ -0,0 +1,19 @@ +{ + "rebuild" : { + "imageRef" : "http://openstack.example.com/v2/32278/images/70a599e0-31e7-49b7-b260-868f441e862b", + "name" : "new-server-test", + "adminPass" : "seekr3t", + "accessIPv4" : "1.2.3.4", + "accessIPv6" : "fe80::100", + "metadata" : { + "meta var" : "meta val" + }, + "personality" : [ + { + "path" : "/etc/banner.txt", + "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ], + "preserve_ephemeral": true + } +} \ No newline at end of file diff --git a/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.xml b/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.xml new file mode 100644 index 000000000000..e755dfa8bc90 --- /dev/null +++ b/doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.xml @@ -0,0 +1,26 @@ + + + + Apache1 + + + + ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp + dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k + IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs + c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g + QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo + ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv + dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy + c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 + b25zLiINCg0KLVJpY2hhcmQgQmFjaA== + + + \ No newline at end of file diff --git a/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-req.json b/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-req.json new file mode 100644 index 000000000000..d88eb4122223 --- /dev/null +++ b/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-req.json @@ -0,0 +1,16 @@ +{ + "server" : { + "name" : "new-server-test", + "imageRef" : "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b", + "flavorRef" : "http://openstack.example.com/openstack/flavors/1", + "metadata" : { + "My Server Name" : "Apache1" + }, + "personality" : [ + { + "path" : "/etc/banner.txt", + "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ] + } +} \ No newline at end of file diff --git a/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-req.xml b/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-req.xml new file mode 100644 index 000000000000..0a3c8bb5303d --- /dev/null +++ b/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-req.xml @@ -0,0 +1,19 @@ + + + + Apache1 + + + + ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp + dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k + IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs + c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g + QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo + ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv + dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy + c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 + b25zLiINCg0KLVJpY2hhcmQgQmFjaA== + + + \ No newline at end of file diff --git a/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.json b/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.json new file mode 100644 index 000000000000..341fee141cba --- /dev/null +++ b/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.json @@ -0,0 +1,16 @@ +{ + "server": { + "adminPass": "8SV3AwzrE9Yn", + "id": "5b39f651-2c68-48a1-ad26-dab135c4f543", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/5b39f651-2c68-48a1-ad26-dab135c4f543", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/5b39f651-2c68-48a1-ad26-dab135c4f543", + "rel": "bookmark" + } + ] + } +} \ No newline at end of file diff --git a/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.xml b/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.xml new file mode 100644 index 000000000000..ecdb860089e1 --- /dev/null +++ b/doc/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/doc/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.json b/doc/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.json new file mode 100644 index 000000000000..4da2c1a74b54 --- /dev/null +++ b/doc/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.json @@ -0,0 +1,17 @@ +{ + "rebuild" : { + "image_ref" : "http://glance.openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b", + "name" : "foobar", + "admin_password" : "seekr3t", + "metadata" : { + "meta_var" : "meta_val" + }, + "personality" : [ + { + "path" : "/etc/banner.txt", + "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ], + "preserve_ephemeral": true + } +} \ No newline at end of file diff --git a/doc/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.xml b/doc/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.xml new file mode 100644 index 000000000000..46e66509b73d --- /dev/null +++ b/doc/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.xml @@ -0,0 +1,24 @@ + + + + meta_val + + + + ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp + dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k + IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs + c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g + QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo + ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv + dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy + c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 + b25zLiINCg0KLVJpY2hhcmQgQmFjaA== + + + \ No newline at end of file diff --git a/nova/api/openstack/compute/contrib/preserve_ephemeral_rebuild.py b/nova/api/openstack/compute/contrib/preserve_ephemeral_rebuild.py new file mode 100644 index 000000000000..872d70276ffa --- /dev/null +++ b/nova/api/openstack/compute/contrib/preserve_ephemeral_rebuild.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.api.openstack import extensions + + +class Preserve_ephemeral_rebuild(extensions.ExtensionDescriptor): + """Allow preservation of the ephemeral partition on rebuild.""" + + name = "PreserveEphemeralOnRebuild" + alias = "os-preserve-ephemeral-rebuild" + namespace = ("http://docs.openstack.org/compute/ext/" + "preserve_ephemeral_rebuild/api/v2") + updated = "2013-12-17T00:00:00+00:00" diff --git a/nova/api/openstack/compute/plugins/v3/servers.py b/nova/api/openstack/compute/plugins/v3/servers.py index 13c331679976..85a97600dd5f 100644 --- a/nova/api/openstack/compute/plugins/v3/servers.py +++ b/nova/api/openstack/compute/plugins/v3/servers.py @@ -250,6 +250,10 @@ class ActionDeserializer(CommonDeserializer): if node.hasAttribute("admin_password"): rebuild["admin_password"] = node.getAttribute("admin_password") + if node.hasAttribute("preserve_ephemeral"): + rebuild["preserve_ephemeral"] = strutils.bool_from_string( + node.getAttribute("preserve_ephemeral"), strict=True) + if self.controller: self.controller.server_rebuild_xml_deserialize(node, rebuild) return rebuild @@ -1110,10 +1114,15 @@ class ServersController(wsgi.Controller): 'metadata': 'metadata', } + rebuild_kwargs = {} + if 'name' in rebuild_dict: self._validate_server_name(rebuild_dict['name']) - rebuild_kwargs = {} + if 'preserve_ephemeral' in rebuild_dict: + rebuild_kwargs['preserve_ephemeral'] = strutils.bool_from_string( + rebuild_dict['preserve_ephemeral'], strict=True) + if list(self.rebuild_extension_manager): self.rebuild_extension_manager.map(self._rebuild_extension_point, rebuild_dict, rebuild_kwargs) diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index e8c37eeaceb4..75f9dc856043 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -413,6 +413,10 @@ class ActionDeserializer(CommonDeserializer): if node.hasAttribute("accessIPv6"): rebuild["accessIPv6"] = node.getAttribute("accessIPv6") + if node.hasAttribute("preserve_ephemeral"): + rebuild["preserve_ephemeral"] = strutils.bool_from_string( + node.getAttribute("preserve_ephemeral"), strict=True) + return rebuild def _action_resize(self, node): @@ -1319,6 +1323,15 @@ class Controller(wsgi.Controller): 'auto_disk_config': 'auto_disk_config', } + kwargs = {} + + # take the preserve_ephemeral value into account only when the + # corresponding extension is active + if (self.ext_mgr.is_loaded('os-preserve-ephemeral-rebuild') + and 'preserve_ephemeral' in body): + kwargs['preserve_ephemeral'] = strutils.bool_from_string( + body['preserve_ephemeral'], strict=True) + if 'accessIPv4' in body: self._validate_access_ipv4(body['accessIPv4']) @@ -1328,8 +1341,6 @@ class Controller(wsgi.Controller): if 'name' in body: self._validate_server_name(body['name']) - kwargs = {} - for request_attribute, instance_attribute in attr_map.items(): try: kwargs[instance_attribute] = body[request_attribute] diff --git a/nova/cells/rpcapi.py b/nova/cells/rpcapi.py index f6de3559f74e..c2baa7d017dd 100644 --- a/nova/cells/rpcapi.py +++ b/nova/cells/rpcapi.py @@ -577,7 +577,7 @@ class CellsAPI(rpcclient.RpcProxy): def rebuild_instance(self, ctxt, instance, new_pass, injected_files, image_ref, orig_image_ref, orig_sys_metadata, bdms, recreate=False, on_shared_storage=False, host=None, - kwargs=None): + preserve_ephemeral=False, kwargs=None): if not CONF.cells.enable: return @@ -585,4 +585,4 @@ class CellsAPI(rpcclient.RpcProxy): cctxt.cast(ctxt, 'rebuild_instance', instance=instance, image_href=image_ref, admin_password=new_pass, files_to_inject=injected_files, - kwargs=kwargs) + preserve_ephemeral=preserve_ephemeral, kwargs=kwargs) diff --git a/nova/compute/api.py b/nova/compute/api.py index 8dde91ff4d16..d85b41ec29ad 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -2060,6 +2060,7 @@ class API(base.Base): orig_image_ref = instance.image_ref or '' files_to_inject = files_to_inject or [] metadata = kwargs.get('metadata', {}) + preserve_ephemeral = kwargs.get('preserve_ephemeral', False) image_id, image = self._get_image(context, image_href) self._check_auto_disk_config(image=image, **kwargs) @@ -2122,7 +2123,7 @@ class API(base.Base): new_pass=admin_password, injected_files=files_to_inject, image_ref=image_href, orig_image_ref=orig_image_ref, orig_sys_metadata=orig_sys_metadata, bdms=bdms, - kwargs=kwargs) + preserve_ephemeral=preserve_ephemeral, kwargs=kwargs) @wrap_check_policy @check_instance_lock diff --git a/nova/compute/manager.py b/nova/compute/manager.py index e9b92664f607..1cf860e413c8 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -416,7 +416,7 @@ class ComputeVirtAPI(virtapi.VirtAPI): class ComputeManager(manager.Manager): """Manages the running instances from creation to destruction.""" - RPC_API_VERSION = '3.4' + RPC_API_VERSION = '3.5' def __init__(self, compute_driver=None, *args, **kwargs): """Load configuration options and connect to the hypervisor.""" @@ -2132,7 +2132,13 @@ class ComputeManager(manager.Manager): injected_files, admin_password, bdms, detach_block_devices, attach_block_devices, network_info=None, - recreate=False, block_device_info=None): + recreate=False, block_device_info=None, + preserve_ephemeral=False): + if preserve_ephemeral: + # The default code path does not support preserving ephemeral + # partitions. + raise exception.PreserveEphemeralNotSupported() + detach_block_devices(context, bdms) if not recreate: @@ -2152,6 +2158,7 @@ class ComputeManager(manager.Manager): admin_password, network_info=network_info, block_device_info=new_block_device_info) + @rpc_common.client_exceptions(exception.PreserveEphemeralNotSupported) @object_compat @wrap_exception() @reverts_task_state @@ -2159,7 +2166,8 @@ class ComputeManager(manager.Manager): @wrap_instance_fault def rebuild_instance(self, context, instance, orig_image_ref, image_ref, injected_files, new_pass, orig_sys_metadata, - bdms, recreate, on_shared_storage): + bdms, recreate, on_shared_storage, + preserve_ephemeral=False): """Destroy and re-make this instance. A 'rebuild' effectively purges all existing data from the system and @@ -2177,6 +2185,8 @@ class ComputeManager(manager.Manager): hypervisor it was on failed) - cleanup of old state will be skipped. :param on_shared_storage: True if instance files on shared storage + :param preserve_ephemeral: True if the default ephemeral storage + partition must be preserved on rebuild """ context = context.elevated() @@ -2275,7 +2285,8 @@ class ComputeManager(manager.Manager): detach_block_devices=detach_block_devices, attach_block_devices=self._prep_block_device, block_device_info=block_device_info, - network_info=network_info) + network_info=network_info, + preserve_ephemeral=preserve_ephemeral) try: self.driver.rebuild(**kwargs) except NotImplementedError: diff --git a/nova/compute/rpcapi.py b/nova/compute/rpcapi.py index 75feb09d9daf..74a3a1ba394f 100644 --- a/nova/compute/rpcapi.py +++ b/nova/compute/rpcapi.py @@ -216,6 +216,7 @@ class ComputeAPI(rpcclient.RpcProxy): 3.2 - Update get_vnc_console() to take an instance object 3.3 - Update validate_console_port() to take an instance object 3.4 - Update rebuild_instance() to take an instance object + 3.5 - Pass preserve_ephemeral flag to rebuild_instance() ''' # @@ -561,15 +562,20 @@ class ComputeAPI(rpcclient.RpcProxy): def rebuild_instance(self, ctxt, instance, new_pass, injected_files, image_ref, orig_image_ref, orig_sys_metadata, bdms, recreate=False, on_shared_storage=False, host=None, - kwargs=None): + preserve_ephemeral=False, kwargs=None): # NOTE(danms): kwargs is only here for cells compatibility, don't # actually send it to compute - if self.can_send_version('3.4'): + extra = {} + if self.can_send_version('3.5'): + version = '3.5' + extra['preserve_ephemeral'] = preserve_ephemeral + elif self.can_send_version('3.4'): version = '3.4' else: # NOTE(russellb) Havana compat version = self._get_compat_version('3.0', '2.22') instance = jsonutils.to_primitive(instance) + bdms_p = jsonutils.to_primitive(bdms) cctxt = self.client.prepare(server=_compute_host(host, instance), version=version) @@ -578,7 +584,8 @@ class ComputeAPI(rpcclient.RpcProxy): injected_files=injected_files, image_ref=image_ref, orig_image_ref=orig_image_ref, orig_sys_metadata=orig_sys_metadata, bdms=bdms_p, - recreate=recreate, on_shared_storage=on_shared_storage) + recreate=recreate, on_shared_storage=on_shared_storage, + **extra) def refresh_provider_fw_rules(self, ctxt, host): # NOTE(russellb) Havana compat diff --git a/nova/exception.py b/nova/exception.py index 9916739b7ce7..add4187c1aec 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -535,6 +535,11 @@ class ImageNotFound(NotFound): msg_fmt = _("Image %(image_id)s could not be found.") +class PreserveEphemeralNotSupported(Invalid): + msg_fmt = _("The current driver does not support " + "preserving ephemeral partitions.") + + # NOTE(jruzicka): ImageNotFound is not a valid EC2 error code. class ImageNotFoundEC2(ImageNotFound): msg_fmt = _("Image %(image_id)s could not be found. The nova EC2 API " diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_server_actions.py b/nova/tests/api/openstack/compute/plugins/v3/test_server_actions.py index 1d7c24802305..3445c3788270 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_server_actions.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_server_actions.py @@ -566,6 +566,43 @@ class ServerActionsControllerTest(test.TestCase): self.assertEqual(instance_meta['kernel_id'], '1') self.assertEqual(instance_meta['ramdisk_id'], '2') + def _test_rebuild_preserve_ephemeral(self, value=None): + return_server = fakes.fake_instance_get(image_ref='2', + vm_state=vm_states.ACTIVE, + host='fake_host') + self.stubs.Set(db, 'instance_get_by_uuid', return_server) + + body = { + "rebuild": { + "image_ref": self._image_href, + }, + } + if value is not None: + body['rebuild']['preserve_ephemeral'] = value + + req = fakes.HTTPRequestV3.blank(self.url) + context = req.environ['nova.context'] + + self.mox.StubOutWithMock(compute_api.API, 'rebuild') + if value is not None: + compute_api.API.rebuild(context, mox.IgnoreArg(), self._image_href, + mox.IgnoreArg(), preserve_ephemeral=value) + else: + compute_api.API.rebuild(context, mox.IgnoreArg(), self._image_href, + mox.IgnoreArg()) + self.mox.ReplayAll() + + self.controller._action_rebuild(req, FAKE_UUID, body) + + def test_rebuild_preserve_ephemeral_true(self): + self._test_rebuild_preserve_ephemeral(True) + + def test_rebuild_preserve_ephemeral_false(self): + self._test_rebuild_preserve_ephemeral(False) + + def test_rebuild_preserve_ephemeral_default(self): + self._test_rebuild_preserve_ephemeral() + def test_resize_server(self): body = dict(resize=dict(flavor_ref="http://localhost/3")) diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_servers.py b/nova/tests/api/openstack/compute/plugins/v3/test_servers.py index 598ba6365390..2f6a7943e915 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_servers.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_servers.py @@ -4430,6 +4430,24 @@ class TestServerRebuildXMLDeserializer(test.NoDBTestCase): } self.assertThat(request['body'], matchers.DictMatches(expected)) + def test_rebuild_preserve_ephemeral_passed(self): + serial_request = """ + + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "rebuild": { + "name": "new-server-test", + "image_ref": "http://localhost/images/1", + "preserve_ephemeral": True, + }, + } + self.assertThat(request['body'], matchers.DictMatches(expected)) + class FakeExt(extensions.V3APIExtensionBase): name = "AccessIPs" diff --git a/nova/tests/api/openstack/compute/test_server_actions.py b/nova/tests/api/openstack/compute/test_server_actions.py index 3c11732cdeab..ff84ae09b424 100644 --- a/nova/tests/api/openstack/compute/test_server_actions.py +++ b/nova/tests/api/openstack/compute/test_server_actions.py @@ -100,7 +100,11 @@ class ServerActionsControllerTest(test.TestCase): self.url = '/v2/fake/servers/%s/action' % self.uuid self._image_href = '155d900f-4e14-4e4c-a73d-069cbf4541e6' - self.controller = servers.Controller() + class FakeExtManager(object): + def is_loaded(self, ext): + return False + + self.controller = servers.Controller(ext_mgr=FakeExtManager()) self.compute_api = self.controller.compute_api self.context = context.RequestContext('fake', 'fake') self.app = fakes.wsgi_app(init_only=('servers',), @@ -320,6 +324,71 @@ class ServerActionsControllerTest(test.TestCase): self.controller._action_reboot, req, FAKE_UUID, body) + def test_rebuild_preserve_ephemeral_is_ignored_when_ext_not_loaded(self): + return_server = fakes.fake_instance_get(image_ref='2', + vm_state=vm_states.ACTIVE, + host='fake_host') + self.stubs.Set(db, 'instance_get_by_uuid', return_server) + + body = { + "rebuild": { + "imageRef": self._image_href, + "preserve_ephemeral": False, + }, + } + req = fakes.HTTPRequest.blank(self.url) + context = req.environ['nova.context'] + + self.mox.StubOutWithMock(compute_api.API, 'rebuild') + compute_api.API.rebuild(context, mox.IgnoreArg(), self._image_href, + mox.IgnoreArg(), files_to_inject=None) + self.mox.ReplayAll() + + self.controller._action_rebuild(req, FAKE_UUID, body) + + def _test_rebuild_preserve_ephemeral(self, value=None): + def fake_is_loaded(ext): + return ext == 'os-preserve-ephemeral-rebuild' + self.stubs.Set(self.controller.ext_mgr, 'is_loaded', fake_is_loaded) + + return_server = fakes.fake_instance_get(image_ref='2', + vm_state=vm_states.ACTIVE, + host='fake_host') + self.stubs.Set(db, 'instance_get_by_uuid', return_server) + + body = { + "rebuild": { + "imageRef": self._image_href, + }, + } + if value is not None: + body['rebuild']['preserve_ephemeral'] = value + + req = fakes.HTTPRequest.blank(self.url) + context = req.environ['nova.context'] + + self.mox.StubOutWithMock(compute_api.API, 'rebuild') + + if value is not None: + compute_api.API.rebuild(context, mox.IgnoreArg(), self._image_href, + mox.IgnoreArg(), preserve_ephemeral=value, + files_to_inject=None) + else: + compute_api.API.rebuild(context, mox.IgnoreArg(), self._image_href, + mox.IgnoreArg(), files_to_inject=None) + self.mox.ReplayAll() + + self.controller._action_rebuild(req, FAKE_UUID, body) + + def test_rebuild_preserve_ephemeral_true(self): + self._test_rebuild_preserve_ephemeral(True) + + def test_rebuild_preserve_ephemeral_false(self): + self._test_rebuild_preserve_ephemeral(False) + + def test_rebuild_preserve_ephemeral_default(self): + self._test_rebuild_preserve_ephemeral() + def test_rebuild_accepted_minimum(self): return_server = fakes.fake_instance_get(image_ref='2', vm_state=vm_states.ACTIVE, host='fake_host') @@ -1361,6 +1430,21 @@ class TestServerActionXMLDeserializer(test.TestCase): serial_request, 'action') + def test_rebuild_preserve_ephemeral_passed(self): + serial_request = """ + """ + request = self.deserializer.deserialize(serial_request, 'action') + expected = { + "rebuild": { + "imageRef": "http://localhost/images/1", + "preserve_ephemeral": True, + }, + } + self.assertThat(request['body'], matchers.DictMatches(expected)) + def test_corrupt_xml(self): """Should throw a 400 error on corrupt xml.""" self.assertRaises( diff --git a/nova/tests/compute/test_rpcapi.py b/nova/tests/compute/test_rpcapi.py index 5ed56f1258df..e6f119244b63 100644 --- a/nova/tests/compute/test_rpcapi.py +++ b/nova/tests/compute/test_rpcapi.py @@ -473,6 +473,7 @@ class ComputeRpcAPITestCase(test.TestCase): reboot_type='type', version='2.32') def test_rebuild_instance(self): + self.flags(compute='3.4', group='upgrade_levels') self._test_compute_api('rebuild_instance', 'cast', new_pass='None', injected_files='None', image_ref='None', orig_image_ref='None', bdms=[], instance=self.fake_instance, host='new_host', @@ -487,6 +488,14 @@ class ComputeRpcAPITestCase(test.TestCase): orig_sys_metadata=None, recreate=True, on_shared_storage=True, version='2.22') + def test_rebuild_instance_preserve_ephemeral(self): + self.flags(compute='3.5', group='upgrade_levels') + self._test_compute_api('rebuild_instance', 'cast', new_pass='None', + injected_files='None', image_ref='None', orig_image_ref='None', + bdms=[], instance=self.fake_instance, host='new_host', + orig_sys_metadata=None, recreate=True, on_shared_storage=True, + preserve_ephemeral=True, version='3.5') + def test_reserve_block_device_name(self): self._test_compute_api('reserve_block_device_name', 'call', instance=self.fake_instance, device='device', volume_id='id') diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl index 39f96d8d3f30..c3ae0280047d 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl @@ -615,6 +615,14 @@ "name": "BareMetalExtStatus", "namespace": "http://docs.openstack.org/compute/ext/baremetal_ext_status/api/v2", "updated": "%(timestamp)s" + }, + { + "alias": "os-preserve-ephemeral-rebuild", + "description": "%(text)s", + "links": [], + "name": "PreserveEphemeralOnRebuild", + "namespace": "http://docs.openstack.org/compute/ext/preserve_ephemeral_rebuild/api/v2", + "updated": "%(timestamp)s" } ] } diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl index b74da743d103..e8715ee8f77d 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl @@ -230,4 +230,7 @@ %(text)s + + %(text)s + diff --git a/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.json.tpl b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.json.tpl new file mode 100644 index 000000000000..9c16a15f058b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.json.tpl @@ -0,0 +1,55 @@ +{ + "server": { + "accessIPv4": "%(ip)s", + "accessIPv6": "%(ip6)s", + "addresses": { + "private": [ + { + "addr": "%(ip)s", + "version": 4 + } + ] + }, + "adminPass": "%(password)s", + "created": "%(timestamp)s", + "flavor": { + "id": "1", + "links": [ + { + "href": "%(host)s/openstack/flavors/1", + "rel": "bookmark" + } + ] + }, + "hostId": "%(hostid)s", + "id": "%(uuid)s", + "image": { + "id": "%(uuid)s", + "links": [ + { + "href": "%(host)s/openstack/images/%(uuid)s", + "rel": "bookmark" + } + ] + }, + "links": [ + { + "href": "%(host)s/v2/openstack/servers/%(uuid)s", + "rel": "self" + }, + { + "href": "%(host)s/openstack/servers/%(uuid)s", + "rel": "bookmark" + } + ], + "metadata": { + "meta var": "meta val" + }, + "name": "%(name)s", + "progress": 0, + "status": "ACTIVE", + "tenant_id": "openstack", + "updated": "%(timestamp)s", + "user_id": "fake" + } +} diff --git a/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.xml.tpl b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.xml.tpl new file mode 100644 index 000000000000..d9e80f8c417a --- /dev/null +++ b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.xml.tpl @@ -0,0 +1,37 @@ + + + + + + + + + + Apache1 + + + + + + + + + diff --git a/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.json.tpl b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.json.tpl new file mode 100644 index 000000000000..2f06fd7008d9 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.json.tpl @@ -0,0 +1,19 @@ +{ + "rebuild" : { + "imageRef" : "%(host)s/v2/32278/images/%(uuid)s", + "name" : "%(name)s", + "adminPass" : "%(pass)s", + "accessIPv4" : "%(ip)s", + "accessIPv6" : "%(ip6)s", + "metadata" : { + "meta var" : "meta val" + }, + "personality" : [ + { + "path" : "/etc/banner.txt", + "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ], + "preserve_ephemeral": %(preserve_ephemeral)s + } +} diff --git a/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.xml.tpl b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.xml.tpl new file mode 100644 index 000000000000..6d469d40ea7f --- /dev/null +++ b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.xml.tpl @@ -0,0 +1,26 @@ + + + + Apache1 + + + + ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp + dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k + IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs + c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g + QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo + ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv + dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy + c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 + b25zLiINCg0KLVJpY2hhcmQgQmFjaA== + + + diff --git a/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-req.json.tpl b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-req.json.tpl new file mode 100644 index 000000000000..d3916d1aa68a --- /dev/null +++ b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-req.json.tpl @@ -0,0 +1,16 @@ +{ + "server" : { + "name" : "new-server-test", + "imageRef" : "%(host)s/openstack/images/%(image_id)s", + "flavorRef" : "%(host)s/openstack/flavors/1", + "metadata" : { + "My Server Name" : "Apache1" + }, + "personality" : [ + { + "path" : "/etc/banner.txt", + "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ] + } +} diff --git a/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-req.xml.tpl b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-req.xml.tpl new file mode 100644 index 000000000000..f92614984242 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-req.xml.tpl @@ -0,0 +1,19 @@ + + + + Apache1 + + + + ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp + dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k + IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs + c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g + QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo + ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv + dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy + c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 + b25zLiINCg0KLVJpY2hhcmQgQmFjaA== + + + diff --git a/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.json.tpl new file mode 100644 index 000000000000..d5f030c8730b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.json.tpl @@ -0,0 +1,16 @@ +{ + "server": { + "adminPass": "%(password)s", + "id": "%(id)s", + "links": [ + { + "href": "%(host)s/v2/openstack/servers/%(uuid)s", + "rel": "self" + }, + { + "href": "%(host)s/openstack/servers/%(uuid)s", + "rel": "bookmark" + } + ] + } +} diff --git a/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.xml.tpl new file mode 100644 index 000000000000..3bb13e69bd6d --- /dev/null +++ b/nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.xml.tpl @@ -0,0 +1,6 @@ + + + + + + diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index 7498dfce85a0..0c6d840883b1 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -3982,3 +3982,61 @@ class MigrationsSamplesJsonTest(ApiSampleTestBaseV2): class MigrationsSamplesXmlTest(MigrationsSamplesJsonTest): ctype = 'xml' + + +class PreserveEphemeralOnRebuildJsonTest(ServersSampleBase): + extension_name = ('nova.api.openstack.compute.contrib.' + 'preserve_ephemeral_rebuild.' + 'Preserve_ephemeral_rebuild') + + def _test_server_action(self, uuid, action, + subs={}, resp_tpl=None, code=202): + subs.update({'action': action}) + response = self._do_post('servers/%s/action' % uuid, + 'server-action-%s' % action.lower(), + subs) + if resp_tpl: + subs.update(self._get_regexes()) + self._verify_response(resp_tpl, subs, response, code) + else: + self.assertEqual(response.status, code) + self.assertEqual(response.read(), "") + + def test_rebuild_server_preserve_ephemeral_false(self): + uuid = self._post_server() + image = self.api.get_images()[0]['id'] + subs = {'host': self._get_host(), + 'uuid': image, + 'name': 'foobar', + 'pass': 'seekr3t', + 'ip': '1.2.3.4', + 'ip6': 'fe80::100', + 'hostid': '[a-f0-9]+', + 'preserve_ephemeral': 'false'} + self._test_server_action(uuid, 'rebuild', subs, + 'server-action-rebuild-resp') + + def test_rebuild_server_preserve_ephemeral_true(self): + image = self.api.get_images()[0]['id'] + subs = {'host': self._get_host(), + 'uuid': image, + 'name': 'new-server-test', + 'pass': 'seekr3t', + 'ip': '1.2.3.4', + 'ip6': 'fe80::100', + 'hostid': '[a-f0-9]+', + 'preserve_ephemeral': 'true'} + + def fake_rebuild(self_, context, instance, image_href, admin_password, + **kwargs): + self.assertTrue(kwargs['preserve_ephemeral']) + self.stubs.Set(compute_api.API, 'rebuild', fake_rebuild) + + instance_uuid = self._post_server() + response = self._do_post('servers/%s/action' % instance_uuid, + 'server-action-rebuild', subs) + self.assertEqual(response.status, 202) + + +class PreserveEphemeralOnRebuildXmlTest(PreserveEphemeralOnRebuildJsonTest): + ctype = 'xml' diff --git a/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral-resp.json.tpl b/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral-resp.json.tpl new file mode 100644 index 000000000000..51deae4eb424 --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral-resp.json.tpl @@ -0,0 +1,55 @@ +{ + "server": { + "addresses": { + "private": [ + { + "addr": "%(ip)s", + "version": 4, + "mac_addr": "aa:bb:cc:dd:ee:ff", + "type": "fixed" + } + ] + }, + "admin_password": "%(password)s", + "created": "%(timestamp)s", + "flavor": { + "id": "1", + "links": [ + { + "href": "%(host)s/flavors/1", + "rel": "bookmark" + } + ] + }, + "host_id": "%(hostid)s", + "id": "%(uuid)s", + "image": { + "id": "%(uuid)s", + "links": [ + { + "href": "%(glance_host)s/images/%(uuid)s", + "rel": "bookmark" + } + ] + }, + "links": [ + { + "href": "%(host)s/v3/servers/%(uuid)s", + "rel": "self" + }, + { + "href": "%(host)s/servers/%(uuid)s", + "rel": "bookmark" + } + ], + "metadata": { + "meta_var": "meta_val" + }, + "name": "%(name)s", + "progress": 0, + "status": "ACTIVE", + "tenant_id": "openstack", + "updated": "%(timestamp)s", + "user_id": "fake" + } +} diff --git a/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral-resp.xml.tpl b/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral-resp.xml.tpl new file mode 100644 index 000000000000..bf3a06fead29 --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral-resp.xml.tpl @@ -0,0 +1,35 @@ + + + + + + + + + + meta_val + + + + + + + + + diff --git a/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.json.tpl b/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.json.tpl new file mode 100644 index 000000000000..7b042642b05f --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.json.tpl @@ -0,0 +1,17 @@ +{ + "rebuild" : { + "image_ref" : "%(glance_host)s/images/%(uuid)s", + "name" : "%(name)s", + "admin_password" : "%(pass)s", + "metadata" : { + "meta_var" : "meta_val" + }, + "personality" : [ + { + "path" : "/etc/banner.txt", + "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 b25zLiINCg0KLVJpY2hhcmQgQmFjaA==" + } + ], + "preserve_ephemeral": %(preserve_ephemeral)s + } +} diff --git a/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.xml.tpl b/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.xml.tpl new file mode 100644 index 000000000000..fd04f9a09193 --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.xml.tpl @@ -0,0 +1,24 @@ + + + + meta_val + + + + ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp + dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k + IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs + c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g + QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo + ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv + dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy + c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 + b25zLiINCg0KLVJpY2hhcmQgQmFjaA== + + + diff --git a/nova/tests/integrated/v3/test_servers.py b/nova/tests/integrated/v3/test_servers.py index 4d7cd4692db2..adeb04ffee41 100644 --- a/nova/tests/integrated/v3/test_servers.py +++ b/nova/tests/integrated/v3/test_servers.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +from nova.compute import api as compute_api from nova.tests.image import fake from nova.tests.integrated.v3 import api_sample_base @@ -116,6 +117,35 @@ class ServersActionsJsonTest(ServersSampleBase): self._test_server_action(uuid, 'rebuild', subs, 'server-action-rebuild-resp') + def _test_server_rebuild_preserve_ephemeral(self, value): + uuid = self._post_server() + image = fake.get_valid_image_id() + subs = {'host': self._get_host(), + 'uuid': image, + 'name': 'foobar', + 'pass': 'seekr3t', + 'hostid': '[a-f0-9]+', + 'preserve_ephemeral': str(value).lower(), + 'action': 'rebuild', + 'glance_host': self._get_glance_host(), + } + + def fake_rebuild(self_, context, instance, image_href, admin_password, + files_to_inject=None, **kwargs): + self.assertEqual(kwargs['preserve_ephemeral'], value) + self.stubs.Set(compute_api.API, 'rebuild', fake_rebuild) + + response = self._do_post('servers/%s/action' % uuid, + 'server-action-rebuild-preserve-ephemeral', + subs) + self.assertEqual(response.status, 202) + + def test_server_rebuild_preserve_ephemeral_true(self): + self._test_server_rebuild_preserve_ephemeral(True) + + def test_server_rebuild_preserve_ephemeral_false(self): + self._test_server_rebuild_preserve_ephemeral(False) + def test_server_resize(self): self.flags(allow_resize_to_same_host=True) uuid = self._post_server() diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 15a40a28626e..ca0380a0d097 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -217,7 +217,8 @@ class ComputeDriver(object): def rebuild(self, context, instance, image_meta, injected_files, admin_password, bdms, detach_block_devices, attach_block_devices, network_info=None, - recreate=False, block_device_info=None): + recreate=False, block_device_info=None, + preserve_ephemeral=False): """Destroy and re-make this instance. A 'rebuild' effectively purges all existing data from the system and @@ -250,6 +251,8 @@ class ComputeDriver(object): hypervisor - all the cleanup of old state is skipped. :param block_device_info: Information about block devices to be attached to the instance. + :param preserve_ephemeral: True if the default ephemeral storage + partition must be preserved on rebuild """ raise NotImplementedError()