From d5a70de4793a1e44056c55121505edc63fd36969 Mon Sep 17 00:00:00 2001 From: "Leandro I. Costantino" Date: Sat, 8 Feb 2014 12:23:30 -0300 Subject: [PATCH] Add APIv2 support to make host optional on evacuate In the event of an unrecoverable hardware failure, support needs to relocate an instance to another compute so it can be rebuilt. The changes involved in this patch are: [*] Add a new v2 extension to determine that the host argument on evacuate is now optional.(Extended_evacuate_find_host) [*] Doc regeneration. DocImpact: The evacuate target host is now optional. If 'host' field is not sent in the request, the scheduler will determine the target host. This will include nova client changes ( on the proper commit ) to support this new optional parameter. Implements: blueprint find-host-and-evacuate-instance Change-Id: Ib34fb3120263b746ad2f8fe89c14137e36a07a53 Co-Authored-By: Juan M. Olle Co-Authored-By: Andres Buraschi Co-Authored-By: Anuj Mathur Co-Authored-By: Navneet Kumar Co-Authored-By: Claxton Correya --- .../all_extensions/extensions-get-resp.json | 8 ++ .../all_extensions/extensions-get-resp.xml | 5 + .../server-evacuate-find-host-req.json | 6 + .../server-evacuate-find-host-req.xml | 4 + .../server-evacuate-find-host-resp.json | 3 + .../server-evacuate-find-host-resp.xml | 2 + .../server-post-req.json | 16 +++ .../server-post-req.xml | 19 +++ .../server-post-resp.json | 16 +++ .../server-post-resp.xml | 6 + .../api/openstack/compute/contrib/evacuate.py | 27 +++-- .../contrib/extended_evacuate_find_host.py | 26 ++++ .../compute/contrib/test_evacuate.py | 5 + .../test_extended_evacuate_find_host.py | 114 ++++++++++++++++++ .../extensions-get-resp.json.tpl | 8 ++ .../extensions-get-resp.xml.tpl | 3 + .../server-evacuate-find-host-req.json.tpl | 6 + .../server-evacuate-find-host-req.xml.tpl | 5 + .../server-evacuate-find-host-resp.json.tpl | 3 + .../server-evacuate-find-host-resp.xml.tpl | 2 + .../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 | 47 ++++++++ 25 files changed, 379 insertions(+), 9 deletions(-) create mode 100644 doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.json create mode 100644 doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.xml create mode 100644 doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.json create mode 100644 doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.xml create mode 100644 doc/api_samples/os-extended-evacuate-find-host/server-post-req.json create mode 100644 doc/api_samples/os-extended-evacuate-find-host/server-post-req.xml create mode 100644 doc/api_samples/os-extended-evacuate-find-host/server-post-resp.json create mode 100644 doc/api_samples/os-extended-evacuate-find-host/server-post-resp.xml create mode 100644 nova/api/openstack/compute/contrib/extended_evacuate_find_host.py create mode 100644 nova/tests/api/openstack/compute/contrib/test_extended_evacuate_find_host.py create mode 100644 nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-post-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-post-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-post-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-post-resp.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 99229da36bbe..4d4967accf7b 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.json +++ b/doc/api_samples/all_extensions/extensions-get-resp.json @@ -272,6 +272,14 @@ "namespace": "http://docs.openstack.org/compute/ext/evacuate/api/v2", "updated": "2013-01-06T00:00:00Z" }, + { + "alias": "os-extended-evacuate-find-host", + "description": "Enables server evacuation without target host. Scheduler will select\n one to target.\n ", + "links": [], + "name": "ExtendedEvacuateFindHost", + "namespace": "http://docs.openstack.org/compute/ext/extended_evacuate_find_host/api/v2", + "updated": "2014-02-12T00:00:00Z" + }, { "alias": "os-extended-floating-ips", "description": "Adds optional fixed_address to the add floating IP command.", diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml index eb4e6e32d39a..d69e8ab40ace 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.xml +++ b/doc/api_samples/all_extensions/extensions-get-resp.xml @@ -118,6 +118,11 @@ Enables server evacuation. + + Enables server evacuation without target host. Scheduler will select + one to target. + + Adds optional fixed_address to the add floating IP command. diff --git a/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.json b/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.json new file mode 100644 index 000000000000..e9ee83481c2c --- /dev/null +++ b/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.json @@ -0,0 +1,6 @@ +{ + "evacuate": { + "adminPass": "MySecretPass", + "onSharedStorage": "False" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.xml b/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.xml new file mode 100644 index 000000000000..4faf14a785a5 --- /dev/null +++ b/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.json b/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.json new file mode 100644 index 000000000000..6cd942395fea --- /dev/null +++ b/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.json @@ -0,0 +1,3 @@ +{ + "adminPass": "MySecretPass" +} \ No newline at end of file diff --git a/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.xml b/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.xml new file mode 100644 index 000000000000..582388670248 --- /dev/null +++ b/doc/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.xml @@ -0,0 +1,2 @@ + +MySecretPass \ No newline at end of file diff --git a/doc/api_samples/os-extended-evacuate-find-host/server-post-req.json b/doc/api_samples/os-extended-evacuate-find-host/server-post-req.json new file mode 100644 index 000000000000..d88eb4122223 --- /dev/null +++ b/doc/api_samples/os-extended-evacuate-find-host/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-extended-evacuate-find-host/server-post-req.xml b/doc/api_samples/os-extended-evacuate-find-host/server-post-req.xml new file mode 100644 index 000000000000..0a3c8bb5303d --- /dev/null +++ b/doc/api_samples/os-extended-evacuate-find-host/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-extended-evacuate-find-host/server-post-resp.json b/doc/api_samples/os-extended-evacuate-find-host/server-post-resp.json new file mode 100644 index 000000000000..e07dceaeaa74 --- /dev/null +++ b/doc/api_samples/os-extended-evacuate-find-host/server-post-resp.json @@ -0,0 +1,16 @@ +{ + "server": { + "adminPass": "y6hsKno56L6R", + "id": "1c650ba2-6a76-41d1-805c-64f4e312200e", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/1c650ba2-6a76-41d1-805c-64f4e312200e", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/1c650ba2-6a76-41d1-805c-64f4e312200e", + "rel": "bookmark" + } + ] + } +} \ No newline at end of file diff --git a/doc/api_samples/os-extended-evacuate-find-host/server-post-resp.xml b/doc/api_samples/os-extended-evacuate-find-host/server-post-resp.xml new file mode 100644 index 000000000000..ad40d9e731b3 --- /dev/null +++ b/doc/api_samples/os-extended-evacuate-find-host/server-post-resp.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/nova/api/openstack/compute/contrib/evacuate.py b/nova/api/openstack/compute/contrib/evacuate.py index cd6f8f4ccb44..ba5e62ca4a2e 100644 --- a/nova/api/openstack/compute/contrib/evacuate.py +++ b/nova/api/openstack/compute/contrib/evacuate.py @@ -28,15 +28,17 @@ authorize = extensions.extension_authorizer('compute', 'evacuate') class Controller(wsgi.Controller): - def __init__(self, *args, **kwargs): + def __init__(self, ext_mgr, *args, **kwargs): super(Controller, self).__init__(*args, **kwargs) self.compute_api = compute.API() self.host_api = compute.HostAPI() + self.ext_mgr = ext_mgr @wsgi.action('evacuate') def _evacuate(self, req, id, body): """Permit admins to evacuate a server from a failed host to a new one. + If host is empty, the scheduler will select one. """ context = req.environ["nova.context"] authorize(context) @@ -45,12 +47,18 @@ class Controller(wsgi.Controller): raise exc.HTTPBadRequest(_("Malformed request body")) evacuate_body = body["evacuate"] + host = evacuate_body.get("host") + + if (not host and + not self.ext_mgr.is_loaded('os-extended-evacuate-find-host')): + msg = _("host must be specified.") + raise exc.HTTPBadRequest(explanation=msg) + try: - host = evacuate_body["host"] on_shared_storage = strutils.bool_from_string( evacuate_body["onSharedStorage"]) except (TypeError, KeyError): - msg = _("host and onSharedStorage must be specified.") + msg = _("onSharedStorage must be specified.") raise exc.HTTPBadRequest(explanation=msg) password = None @@ -65,11 +73,12 @@ class Controller(wsgi.Controller): elif not on_shared_storage: password = utils.generate_password() - try: - self.host_api.service_get_by_compute_host(context, host) - except exception.NotFound: - msg = _("Compute host %s not found.") % host - raise exc.HTTPNotFound(explanation=msg) + if host is not None: + try: + self.host_api.service_get_by_compute_host(context, host) + except exception.NotFound: + msg = _("Compute host %s not found.") % host + raise exc.HTTPNotFound(explanation=msg) try: instance = self.compute_api.get(context, id, want_objects=True) @@ -99,6 +108,6 @@ class Evacuate(extensions.ExtensionDescriptor): updated = "2013-01-06T00:00:00Z" def get_controller_extensions(self): - controller = Controller() + controller = Controller(self.ext_mgr) extension = extensions.ControllerExtension(self, 'servers', controller) return [extension] diff --git a/nova/api/openstack/compute/contrib/extended_evacuate_find_host.py b/nova/api/openstack/compute/contrib/extended_evacuate_find_host.py new file mode 100644 index 000000000000..2dfe3faff55f --- /dev/null +++ b/nova/api/openstack/compute/contrib/extended_evacuate_find_host.py @@ -0,0 +1,26 @@ +# +# 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 Extended_evacuate_find_host(extensions.ExtensionDescriptor): + """Enables server evacuation without target host. Scheduler will select + one to target. + """ + name = "ExtendedEvacuateFindHost" + alias = "os-extended-evacuate-find-host" + namespace = ("http://docs.openstack.org/compute/ext/" + "extended_evacuate_find_host/api/v2") + updated = "2014-02-12T00:00:00Z" diff --git a/nova/tests/api/openstack/compute/contrib/test_evacuate.py b/nova/tests/api/openstack/compute/contrib/test_evacuate.py index 5ef88da3a722..ed548e7e771c 100644 --- a/nova/tests/api/openstack/compute/contrib/test_evacuate.py +++ b/nova/tests/api/openstack/compute/contrib/test_evacuate.py @@ -69,6 +69,11 @@ class EvacuateTest(test.NoDBTestCase): for _method in self._methods: self.stubs.Set(compute_api.API, _method, fake_compute_api) + self.flags( + osapi_compute_extension=[ + 'nova.api.openstack.compute.contrib.select_extensions'], + osapi_compute_ext_list=['Evacuate']) + def _get_admin_context(self, user_id='fake', project_id='fake'): ctxt = context.get_admin_context() ctxt.user_id = user_id diff --git a/nova/tests/api/openstack/compute/contrib/test_extended_evacuate_find_host.py b/nova/tests/api/openstack/compute/contrib/test_extended_evacuate_find_host.py new file mode 100644 index 000000000000..187ff5a05618 --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_extended_evacuate_find_host.py @@ -0,0 +1,114 @@ +# Copyright 2013 OpenStack Foundation +# +# 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. + +import uuid + +import mock +import webob + +from nova.compute import vm_states +from nova import context +from nova.objects import instance as instance_obj +from nova.openstack.common import jsonutils +from nova import test +from nova.tests.api.openstack import fakes +from nova.tests import fake_instance + + +class ExtendedEvacuateFindHostTest(test.NoDBTestCase): + + def setUp(self): + super(ExtendedEvacuateFindHostTest, self).setUp() + self.flags( + osapi_compute_extension=[ + 'nova.api.openstack.compute.contrib.select_extensions'], + osapi_compute_ext_list=['Extended_evacuate_find_host', + 'Evacuate']) + self.UUID = uuid.uuid4() + + def _get_admin_context(self, user_id='fake', project_id='fake'): + ctxt = context.get_admin_context() + ctxt.user_id = user_id + ctxt.project_id = project_id + return ctxt + + def _fake_compute_api(*args, **kwargs): + return True + + def _fake_compute_api_get(self, context, instance_id, **kwargs): + instance = fake_instance.fake_db_instance(id=1, uuid=uuid, + task_state=None, + host='host1', + vm_state=vm_states.ACTIVE) + instance = instance_obj.Instance._from_db_object(context, + instance_obj.Instance(), + instance) + return instance + + def _fake_service_get_by_compute_host(self, context, host): + return {'host_name': host, + 'service': 'compute', + 'zone': 'nova' + } + + @mock.patch('nova.compute.api.HostAPI.service_get_by_compute_host') + @mock.patch('nova.compute.api.API.get') + @mock.patch('nova.compute.api.API.evacuate') + def test_evacuate_instance_with_no_target(self, evacuate_mock, + api_get_mock, + service_get_mock): + service_get_mock.side_effects = self._fake_service_get_by_compute_host + api_get_mock.side_effects = self._fake_compute_api_get + evacuate_mock.side_effects = self._fake_compute_api + + ctxt = self._get_admin_context() + app = fakes.wsgi_app(fake_auth_context=ctxt) + req = webob.Request.blank('/v2/fake/servers/%s/action' % self.UUID) + req.method = 'POST' + req.body = jsonutils.dumps({ + 'evacuate': { + 'onSharedStorage': 'False', + 'adminPass': 'MyNewPass' + } + }) + req.content_type = 'application/json' + res = req.get_response(app) + self.assertEqual(200, res.status_int) + evacuate_mock.assert_called_once_with(mock.ANY, mock.ANY, None, + mock.ANY, mock.ANY) + + @mock.patch('nova.compute.api.HostAPI.service_get_by_compute_host') + @mock.patch('nova.compute.api.API.get') + def test_no_target_fails_if_extension_not_loaded(self, api_get_mock, + service_get_mock): + self.flags( + osapi_compute_extension=[ + 'nova.api.openstack.compute.contrib.select_extensions'], + osapi_compute_ext_list=['Evacuate']) + service_get_mock.side_effects = self._fake_service_get_by_compute_host + api_get_mock.side_effects = self._fake_compute_api_get + + ctxt = self._get_admin_context() + app = fakes.wsgi_app(fake_auth_context=ctxt) + req = webob.Request.blank('/v2/fake/servers/%s/action' % self.UUID) + req.method = 'POST' + req.body = jsonutils.dumps({ + 'evacuate': { + 'onSharedStorage': 'False', + 'adminPass': 'MyNewPass' + } + }) + req.content_type = 'application/json' + res = req.get_response(app) + self.assertEqual(400, res.status_int) 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 c4089502250a..ac203b83d4ea 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 @@ -671,6 +671,14 @@ "name": "ServerGroups", "namespace": "http://docs.openstack.org/compute/ext/servergroups/api/v2", "updated": "%(isotime)s" + }, + { + "alias": "os-extended-evacuate-find-host", + "description": "%(text)s", + "links": [], + "name": "ExtendedEvacuateFindHost", + "namespace": "http://docs.openstack.org/compute/ext/extended_evacuate_find_host/api/v2", + "updated": "%(isotime)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 08c9b9f3c88c..308d32136bdd 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 @@ -251,4 +251,7 @@ %(text)s + + %(text)s + diff --git a/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.json.tpl b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.json.tpl new file mode 100644 index 000000000000..5e2c2e6ef01a --- /dev/null +++ b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.json.tpl @@ -0,0 +1,6 @@ +{ + "evacuate": { + "adminPass": "%(adminPass)s", + "onSharedStorage": "%(onSharedStorage)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.xml.tpl b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.xml.tpl new file mode 100644 index 000000000000..a86c9e5c8ad6 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-req.xml.tpl @@ -0,0 +1,5 @@ + + + diff --git a/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.json.tpl b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.json.tpl new file mode 100644 index 000000000000..0da07da5b8f0 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.json.tpl @@ -0,0 +1,3 @@ +{ + "adminPass": "%(password)s" +} diff --git a/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.xml.tpl b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.xml.tpl new file mode 100644 index 000000000000..b3b95fdde4aa --- /dev/null +++ b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-evacuate-find-host-resp.xml.tpl @@ -0,0 +1,2 @@ + +%(password)s \ No newline at end of file diff --git a/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-post-req.json.tpl b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-post-req.json.tpl new file mode 100644 index 000000000000..d3916d1aa68a --- /dev/null +++ b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/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-extended-evacuate-find-host/server-post-req.xml.tpl b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-post-req.xml.tpl new file mode 100644 index 000000000000..f92614984242 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/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-extended-evacuate-find-host/server-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-post-resp.json.tpl new file mode 100644 index 000000000000..d5f030c8730b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/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-extended-evacuate-find-host/server-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/server-post-resp.xml.tpl new file mode 100644 index 000000000000..3bb13e69bd6d --- /dev/null +++ b/nova/tests/integrated/api_samples/os-extended-evacuate-find-host/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 29aa19af930f..86494c6813aa 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -3238,6 +3238,53 @@ class EvacuateXmlTest(EvacuateJsonTest): ctype = 'xml' +class EvacuateFindHostSampleJsonTest(ServersSampleBase): + extends_name = ("nova.api.openstack.compute.contrib" + ".evacuate.Evacuate") + + extension_name = ("nova.api.openstack.compute.contrib" + ".extended_evacuate_find_host.Extended_evacuate_find_host") + + @mock.patch('nova.compute.manager.ComputeManager._check_instance_exists') + @mock.patch('nova.compute.api.HostAPI.service_get_by_compute_host') + @mock.patch('nova.conductor.manager.ComputeTaskManager.rebuild_instance') + def test_server_evacuate(self, rebuild_mock, service_get_mock, + check_instance_mock): + self.uuid = self._post_server() + + req_subs = { + "adminPass": "MySecretPass", + "onSharedStorage": 'False' + } + + check_instance_mock.return_value = False + + def fake_service_get_by_compute_host(self, context, host): + return { + 'host_name': host, + 'service': 'compute', + 'zone': 'nova' + } + service_get_mock.side_effect = fake_service_get_by_compute_host + with mock.patch.object(service_group_api.API, 'service_is_up', + return_value=False): + response = self._do_post('servers/%s/action' % self.uuid, + 'server-evacuate-find-host-req', req_subs) + subs = self._get_regexes() + self._verify_response('server-evacuate-find-host-resp', subs, + response, 200) + rebuild_mock.assert_called_once_with(mock.ANY, instance=mock.ANY, + orig_image_ref=mock.ANY, image_ref=mock.ANY, + injected_files=mock.ANY, new_pass="MySecretPass", + orig_sys_metadata=mock.ANY, bdms=mock.ANY, recreate=mock.ANY, + on_shared_storage=False, preserve_ephemeral=mock.ANY, + host=None) + + +class EvacuateFindHostSampleXmlTests(EvacuateFindHostSampleJsonTest): + ctype = "xml" + + class FloatingIpDNSJsonTest(ApiSampleTestBaseV2): extension_name = ("nova.api.openstack.compute.contrib.floating_ip_dns." "Floating_ip_dns")