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()