From 1aa042e0cb5d724ed120aec998f17f980285490a Mon Sep 17 00:00:00 2001 From: Sylvain Bauza Date: Mon, 20 Jun 2016 18:25:13 +0200 Subject: [PATCH] Add support for microversion 2.29 Now the os-evacuate API supports a new body argument called 'force' which helps the operators to bypass the scheduler call in case they provide a host. Also modifies the host_evacuate helper method in the contrib tree to make sure operators also have the force flag in case they need it for a global call. Change-Id: I5272e9809a7d8be482e87548c6a3b11186c5d1e1 Partially-Implements: blueprint check-destination-on-migrations-newton --- novaclient/__init__.py | 2 +- novaclient/tests/unit/v2/fakes.py | 6 +++- novaclient/tests/unit/v2/test_servers.py | 16 +++++++++ novaclient/tests/unit/v2/test_shell.py | 32 ++++++++++++++++++ novaclient/v2/contrib/host_evacuate.py | 18 ++++++++-- novaclient/v2/servers.py | 43 ++++++++++++++++++++++-- novaclient/v2/shell.py | 16 ++++++++- 7 files changed, 125 insertions(+), 8 deletions(-) diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 7980a7b3f..727328f47 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1") # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. -API_MAX_VERSION = api_versions.APIVersion("2.28") +API_MAX_VERSION = api_versions.APIVersion("2.29") diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index bc4c6e638..13681e02a 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -753,7 +753,11 @@ class FakeHTTPClient(base_client.HTTPClient): keys.remove('adminPass') if 'host' in keys: keys.remove('host') - assert set(keys) == set(['onSharedStorage']) + if 'onSharedStorage' in keys: + keys.remove('onSharedStorage') + if 'force' in keys: + keys.remove('force') + assert set(keys) == set() else: raise AssertionError("Unexpected server action: %s" % action) _headers.update(FAKE_RESPONSE_HEADERS) diff --git a/novaclient/tests/unit/v2/test_servers.py b/novaclient/tests/unit/v2/test_servers.py index 8d451af25..70b06f220 100644 --- a/novaclient/tests/unit/v2/test_servers.py +++ b/novaclient/tests/unit/v2/test_servers.py @@ -1179,3 +1179,19 @@ class ServersV226Test(ServersV225Test): ret = s.set_tags(['tag1', 'tag2']) self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('PUT', '/servers/1234/tags') + + +class ServersV229Test(ServersV226Test): + def setUp(self): + super(ServersV229Test, self).setUp() + self.cs.api_version = api_versions.APIVersion("2.29") + + def test_evacuate(self): + s = self.cs.servers.get(1234) + s.evacuate('fake_target_host') + self.assert_called('POST', '/servers/1234/action', + {'evacuate': {'host': 'fake_target_host'}}) + self.cs.servers.evacuate(s, 'fake_target_host', force=True) + self.assert_called('POST', '/servers/1234/action', + {'evacuate': {'host': 'fake_target_host', + 'force': True}}) diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index a36ab82fe..d4a552698 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -2009,6 +2009,23 @@ class ShellTest(utils.TestCase): {'evacuate': {'host': 'target_hyper', 'onSharedStorage': False}}, pos=4) + def test_host_evacuate_v2_29(self): + self.run_command('host-evacuate hyper --target target_hyper --force', + api_version='2.29') + self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) + self.assert_called('POST', '/servers/uuid1/action', + {'evacuate': {'host': 'target_hyper', 'force': True} + }, pos=1) + self.assert_called('POST', '/servers/uuid2/action', + {'evacuate': {'host': 'target_hyper', 'force': True} + }, pos=2) + self.assert_called('POST', '/servers/uuid3/action', + {'evacuate': {'host': 'target_hyper', 'force': True} + }, pos=3) + self.assert_called('POST', '/servers/uuid4/action', + {'evacuate': {'host': 'target_hyper', 'force': True} + }, pos=4) + def test_host_evacuate_with_shared_storage(self): self.run_command( 'host-evacuate --on-shared-storage hyper --target target_hyper') @@ -2410,6 +2427,21 @@ class ShellTest(utils.TestCase): {'evacuate': {'host': 'new_host', 'onSharedStorage': True}}) + def test_evacuate_v2_29(self): + self.run_command('evacuate sample-server new_host', api_version="2.29") + self.assert_called('POST', '/servers/1234/action', + {'evacuate': {'host': 'new_host'}}) + self.run_command('evacuate sample-server new_host ' + '--password NewAdminPass', api_version="2.29") + self.assert_called('POST', '/servers/1234/action', + {'evacuate': {'host': 'new_host', + 'adminPass': 'NewAdminPass'}}) + self.run_command('evacuate --force sample-server new_host', + api_version="2.29") + self.assert_called('POST', '/servers/1234/action', + {'evacuate': {'host': 'new_host', + 'force': True}}) + def test_evacuate_with_no_target_host(self): self.run_command('evacuate sample-server') self.assert_called('POST', '/servers/1234/action', diff --git a/novaclient/v2/contrib/host_evacuate.py b/novaclient/v2/contrib/host_evacuate.py index 2a0335d30..22055656b 100644 --- a/novaclient/v2/contrib/host_evacuate.py +++ b/novaclient/v2/contrib/host_evacuate.py @@ -27,12 +27,17 @@ def _server_evacuate(cs, server, args): success = True error_message = "" try: - on_shared_storage = getattr(args, 'on_shared_storage', None) - if api_versions.APIVersion("2.14") <= cs.api_version: - # if microversion >= 2.14 + if api_versions.APIVersion("2.29") <= cs.api_version: + # if microversion >= 2.29 + force = getattr(args, 'force', None) + cs.servers.evacuate(server=server['uuid'], host=args.target_host, + force=force) + elif api_versions.APIVersion("2.14") <= cs.api_version: + # if microversion 2.14 - 2.28 cs.servers.evacuate(server=server['uuid'], host=args.target_host) else: # else microversion 2.0 - 2.13 + on_shared_storage = getattr(args, 'on_shared_storage', None) cs.servers.evacuate(server=server['uuid'], host=args.target_host, on_shared_storage=on_shared_storage) @@ -60,6 +65,13 @@ def _server_evacuate(cs, server, args): help=_('Specifies whether all instances files are on shared storage'), start_version='2.0', end_version='2.13') +@utils.arg( + '--force', + dest='force', + action='store_true', + default=False, + help=_('Force to not verify the scheduler if a host is provided.'), + start_version='2.29') def do_host_evacuate(cs, args): """Evacuate all instances from failed host.""" hypervisors = cs.hypervisors.search(args.host, servers=True) diff --git a/novaclient/v2/servers.py b/novaclient/v2/servers.py index 39adeaba4..fd672af20 100644 --- a/novaclient/v2/servers.py +++ b/novaclient/v2/servers.py @@ -499,7 +499,7 @@ class Server(base.Resource): """ return self.manager.evacuate(self, host, on_shared_storage, password) - @api_versions.wraps("2.14") + @api_versions.wraps("2.14", "2.28") def evacuate(self, host=None, password=None): """ Evacuate an instance from failed host to specified host. @@ -511,6 +511,19 @@ class Server(base.Resource): """ return self.manager.evacuate(self, host, password) + @api_versions.wraps("2.29") + def evacuate(self, host=None, password=None, force=None): + """ + Evacuate an instance from failed host to specified host. + + :param host: Name of the target host + :param password: string to set as admin password on the evacuated + server. + :param force: forces to bypass the scheduler if host is provided. + :returns: An instance of novaclient.base.TupleWithMeta + """ + return self.manager.evacuate(self, host, password, force) + def interface_list(self): """ List interfaces attached to an instance. @@ -1658,7 +1671,7 @@ class ServerManager(base.BootingManagerWithFind): body) return base.TupleWithMeta((resp, body), resp) - @api_versions.wraps("2.14") + @api_versions.wraps("2.14", "2.28") def evacuate(self, server, host=None, password=None): """ Evacuate a server instance. @@ -1680,6 +1693,32 @@ class ServerManager(base.BootingManagerWithFind): body) return base.TupleWithMeta((resp, body), resp) + @api_versions.wraps("2.29") + def evacuate(self, server, host=None, password=None, force=None): + """ + Evacuate a server instance. + + :param server: The :class:`Server` (or its ID) to share onto. + :param host: Name of the target host. + :param password: string to set as password on the evacuated server. + :param force: forces to bypass the scheduler if host is provided. + :returns: An instance of novaclient.base.TupleWithMeta + """ + + body = {} + if host is not None: + body['host'] = host + + if password is not None: + body['adminPass'] = password + + if force: + body['force'] = force + + resp, body = self._action_return_resp_and_body('evacuate', server, + body) + return base.TupleWithMeta((resp, body), resp) + def interface_list(self, server): """ List attached network interfaces diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 042b35435..7e1241640 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -4688,12 +4688,26 @@ def do_quota_class_update(cs, args): help=_('Specifies whether server files are located on shared storage.'), start_version='2.0', end_version='2.13') +@utils.arg( + '--force', + dest='force', + action='store_true', + default=False, + help=_('Force to not verify the scheduler if a host is provided.'), + start_version='2.29') def do_evacuate(cs, args): """Evacuate server from failed host.""" server = _find_server(cs, args.server) on_shared_storage = getattr(args, 'on_shared_storage', None) - res = server.evacuate(args.host, on_shared_storage, args.password)[1] + force = getattr(args, 'force', None) + update_kwargs = {} + if on_shared_storage is not None: + update_kwargs['on_shared_storage'] = on_shared_storage + if force: + update_kwargs['force'] = force + res = server.evacuate(host=args.host, password=args.password, + **update_kwargs)[1] if isinstance(res, dict): utils.print_dict(res)