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
This commit is contained in:
Sylvain Bauza 2016-06-20 18:25:13 +02:00
parent e91a5937b7
commit 1aa042e0cb
7 changed files with 125 additions and 8 deletions

View File

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

View File

@ -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)

View File

@ -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}})

View File

@ -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',

View File

@ -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)

View File

@ -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

View File

@ -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)