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"])