From 0d678ed4bb355a80211144e698da22b287b23027 Mon Sep 17 00:00:00 2001 From: Aarti Kriplani <aarti.kriplani@RACKSPACE.COM> Date: Fri, 10 May 2013 12:04:42 -0500 Subject: [PATCH] Evacuate each instance from one host to another Added a new extension that adds the ability for admins to evacuate an entire host to another host. This internally uses the existing server.evacuate api. The target host is optional so that a free host will be chosen by the scheduler in the api. Implements: blueprint evacuate-host Change-Id: I2352836d01952e281e15edb9bdd1b912106516d6 --- novaclient/tests/v1_1/fakes.py | 12 +++++ novaclient/tests/v1_1/test_shell.py | 49 ++++++++++++++++++++ novaclient/v1_1/contrib/host_evacuate.py | 59 ++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 novaclient/v1_1/contrib/host_evacuate.py diff --git a/novaclient/tests/v1_1/fakes.py b/novaclient/tests/v1_1/fakes.py index 924d39051..9cbcd518e 100644 --- a/novaclient/tests/v1_1/fakes.py +++ b/novaclient/tests/v1_1/fakes.py @@ -1702,3 +1702,15 @@ class FakeHTTPClient(base_client.HTTPClient): "action": "create", "message": None, "project_id": "04019601fe3648c0abd4f4abfb9e6106"}}) + + def post_servers_uuid1_action(self, **kw): + return 202, {}, {} + + def post_servers_uuid2_action(self, **kw): + return 202, {}, {} + + def post_servers_uuid3_action(self, **kw): + return 202, {}, {} + + def post_servers_uuid4_action(self, **kw): + return 202, {}, {} diff --git a/novaclient/tests/v1_1/test_shell.py b/novaclient/tests/v1_1/test_shell.py index 5b17b2b9e..0ac3ecc3f 100644 --- a/novaclient/tests/v1_1/test_shell.py +++ b/novaclient/tests/v1_1/test_shell.py @@ -942,6 +942,55 @@ class ShellTest(utils.TestCase): self.assert_called( 'POST', '/os-hosts/sample-host/action', {'reboot': None}) + def test_host_evacuate(self): + self.run_command('host-evacuate hyper --target target_hyper') + self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) + self.assert_called('POST', '/servers/uuid1/action', + {'evacuate': {'host': 'target_hyper', + 'onSharedStorage': False}}, pos=1) + self.assert_called('POST', '/servers/uuid2/action', + {'evacuate': {'host': 'target_hyper', + 'onSharedStorage': False}}, pos=2) + self.assert_called('POST', '/servers/uuid3/action', + {'evacuate': {'host': 'target_hyper', + 'onSharedStorage': False}}, pos=3) + self.assert_called('POST', '/servers/uuid4/action', + {'evacuate': {'host': 'target_hyper', + 'onSharedStorage': False}}, pos=4) + + def test_host_evacuate_with_shared_storage(self): + self.run_command( + 'host-evacuate --on-shared-storage hyper --target target_hyper') + self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) + self.assert_called('POST', '/servers/uuid1/action', + {'evacuate': {'host': 'target_hyper', + 'onSharedStorage': True}}, pos=1) + self.assert_called('POST', '/servers/uuid2/action', + {'evacuate': {'host': 'target_hyper', + 'onSharedStorage': True}}, pos=2) + self.assert_called('POST', '/servers/uuid3/action', + {'evacuate': {'host': 'target_hyper', + 'onSharedStorage': True}}, pos=3) + self.assert_called('POST', '/servers/uuid4/action', + {'evacuate': {'host': 'target_hyper', + 'onSharedStorage': True}}, pos=4) + + def test_host_evacuate_with_no_target_host(self): + self.run_command('host-evacuate --on-shared-storage hyper') + self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) + self.assert_called('POST', '/servers/uuid1/action', + {'evacuate': {'host': None, + 'onSharedStorage': True}}, pos=1) + self.assert_called('POST', '/servers/uuid2/action', + {'evacuate': {'host': None, + 'onSharedStorage': True}}, pos=2) + self.assert_called('POST', '/servers/uuid3/action', + {'evacuate': {'host': None, + 'onSharedStorage': True}}, pos=3) + self.assert_called('POST', '/servers/uuid4/action', + {'evacuate': {'host': None, + 'onSharedStorage': True}}, pos=4) + def test_coverage_start(self): self.run_command('coverage-start') self.assert_called('POST', '/os-coverage/action') diff --git a/novaclient/v1_1/contrib/host_evacuate.py b/novaclient/v1_1/contrib/host_evacuate.py new file mode 100644 index 000000000..c8acef8b5 --- /dev/null +++ b/novaclient/v1_1/contrib/host_evacuate.py @@ -0,0 +1,59 @@ +# Copyright 2013 Rackspace Hosting +# All Rights Reserved. +# +# 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 novaclient import base +from novaclient import utils + + +class EvacuateHostResponse(base.Resource): + pass + + +def _server_evacuate(cs, server, args): + success = True + error_message = "" + try: + cs.servers.evacuate(server['uuid'], args.target_host, + args.on_shared_storage) + except Exception as e: + success = False + error_message = "Error while evacuating instance: %s" % e + return EvacuateHostResponse(base.Manager, + {"server_uuid": server['uuid'], + "evacuate_accepted": success, + "error_message": error_message}) + + +@utils.arg('host', metavar='<host>', help='Name of host.') +@utils.arg('--target_host', + metavar='<target_host>', + default=None, + help='Name of target host.') +@utils.arg('--on-shared-storage', + dest='on_shared_storage', + action="store_true", + default=False, + help='Specifies whether all instances files are on shared storage') +def do_host_evacuate(cs, args): + """Evacuate all instances from failed host to specified one.""" + hypervisors = cs.hypervisors.search(args.host, servers=True) + response = [] + for hyper in hypervisors: + if hasattr(hyper, 'servers'): + for server in hyper.servers: + response.append(_server_evacuate(cs, server, args)) + + utils.print_list(response, + ["Server UUID", "Evacuate Accepted", "Error Message"])