From c557472496a93b8e7f1c12d3e294cf0bc36b6519 Mon Sep 17 00:00:00 2001 From: Roman Podoliaka Date: Thu, 5 Dec 2013 17:17:36 +0200 Subject: [PATCH] Add preserve_ephemeral option to rebuild The preserve_ephemeral option for rebuild will preserve the content of the ephemeral partition, making stateful golden image based deployments possible even when clouds haven't deployed Cinder, or the hypervisor doesn't support Cinder (e.g. BareMetal / Ironic). Partial-Bug: #1174154 blueprint: baremetal-preserve-ephemeral Co-Authored-By: Robert Collins Change-Id: Id33d5d4107f89814842a3f0b7f33690dd7e3aadc --- .../all_extensions/extensions-get-resp.json | 8 ++ .../all_extensions/extensions-get-resp.xml | 3 + .../server-action-rebuild-resp.json | 55 ++++++++++++ .../server-action-rebuild-resp.xml | 19 ++++ .../server-action-rebuild.json | 19 ++++ .../server-action-rebuild.xml | 26 ++++++ .../server-post-req.json | 16 ++++ .../server-post-req.xml | 19 ++++ .../server-post-resp.json | 16 ++++ .../server-post-resp.xml | 6 ++ ...ver-action-rebuild-preserve-ephemeral.json | 17 ++++ ...rver-action-rebuild-preserve-ephemeral.xml | 24 ++++++ .../contrib/preserve_ephemeral_rebuild.py | 23 +++++ .../openstack/compute/plugins/v3/servers.py | 11 ++- nova/api/openstack/compute/servers.py | 15 +++- nova/cells/rpcapi.py | 4 +- nova/compute/api.py | 3 +- nova/compute/manager.py | 19 +++- nova/compute/rpcapi.py | 13 ++- nova/exception.py | 5 ++ .../compute/plugins/v3/test_server_actions.py | 37 ++++++++ .../compute/plugins/v3/test_servers.py | 18 ++++ .../openstack/compute/test_server_actions.py | 86 ++++++++++++++++++- nova/tests/compute/test_rpcapi.py | 9 ++ .../extensions-get-resp.json.tpl | 8 ++ .../extensions-get-resp.xml.tpl | 3 + .../server-action-rebuild-resp.json.tpl | 55 ++++++++++++ .../server-action-rebuild-resp.xml.tpl | 37 ++++++++ .../server-action-rebuild.json.tpl | 19 ++++ .../server-action-rebuild.xml.tpl | 26 ++++++ .../server-post-req.json.tpl | 16 ++++ .../server-post-req.xml.tpl | 19 ++++ .../server-post-resp.json.tpl | 16 ++++ .../server-post-resp.xml.tpl | 6 ++ nova/tests/integrated/test_api_samples.py | 58 +++++++++++++ ...n-rebuild-preserve-ephemeral-resp.json.tpl | 55 ++++++++++++ ...on-rebuild-preserve-ephemeral-resp.xml.tpl | 35 ++++++++ ...action-rebuild-preserve-ephemeral.json.tpl | 17 ++++ ...-action-rebuild-preserve-ephemeral.xml.tpl | 24 ++++++ nova/tests/integrated/v3/test_servers.py | 30 +++++++ nova/virt/driver.py | 5 +- 41 files changed, 885 insertions(+), 15 deletions(-) create mode 100644 doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.json create mode 100644 doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.xml create mode 100644 doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.json create mode 100644 doc/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.xml create mode 100644 doc/api_samples/os-preserve-ephemeral-rebuild/server-post-req.json create mode 100644 doc/api_samples/os-preserve-ephemeral-rebuild/server-post-req.xml create mode 100644 doc/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.json create mode 100644 doc/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.xml create mode 100644 doc/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.json create mode 100644 doc/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.xml create mode 100644 nova/api/openstack/compute/contrib/preserve_ephemeral_rebuild.py create mode 100644 nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-action-rebuild.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-preserve-ephemeral-rebuild/server-post-resp.xml.tpl create mode 100644 nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral-resp.json.tpl create mode 100644 nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral-resp.xml.tpl create mode 100644 nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.json.tpl create mode 100644 nova/tests/integrated/v3/api_samples/servers/server-action-rebuild-preserve-ephemeral.xml.tpl diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json index f1aa8aa680..fa780a5562 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 768d4314da..d12e74d80a 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 0000000000..bd44136f0a --- /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 0000000000..bc18e7405b --- /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 0000000000..99a95cde5b --- /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 0000000000..e755dfa8bc --- /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 0000000000..d88eb41222 --- /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 0000000000..0a3c8bb530 --- /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 0000000000..341fee141c --- /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 0000000000..ecdb860089 --- /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 0000000000..4da2c1a74b --- /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 0000000000..46e66509b7 --- /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 0000000000..872d70276f --- /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 13c3316799..85a97600dd 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 e8c37eeace..75f9dc8560 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 f6de3559f7..c2baa7d017 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 8dde91ff4d..d85b41ec29 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 e9b92664f6..1cf860e413 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 75feb09d9d..74a3a1ba39 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 9916739b7c..add4187c1a 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 1d7c248023..3445c37882 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 598ba63653..2f6a7943e9 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 3c11732cde..ff84ae09b4 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 5ed56f1258..e6f119244b 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 39f96d8d3f..c3ae028004 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 b74da743d1..e8715ee8f7 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 0000000000..9c16a15f05 --- /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 0000000000..d9e80f8c41 --- /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 0000000000..2f06fd7008 --- /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 0000000000..6d469d40ea --- /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 0000000000..d3916d1aa6 --- /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 0000000000..f926149842 --- /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 0000000000..d5f030c873 --- /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 0000000000..3bb13e69bd --- /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 7498dfce85..0c6d840883 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 0000000000..51deae4eb4 --- /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 0000000000..bf3a06fead --- /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 0000000000..7b042642b0 --- /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 0000000000..fd04f9a091 --- /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 4d7cd4692d..adeb04ffee 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 15a40a2862..ca0380a0d0 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()