Implement hypervisor hostname exact pattern match

When starting cold migration with nova command "nova host-
    servers-migrate compute-1", the migration started from all
    compute hosts starting with name "compute-1", not only from
    compute-1 host. The same thing happens to "nova host-meta",
    "nova host-evacuate", "nova host-evacuate-live" as well.

    With the "--strict" option added to these nova commands, the
    action will be applied to a single compute with the exact
    hostname string match, but not to the computes with hostname
    substring match. Error handling is also added to these nova
    commands such that when specified hostname name does not exist,
    "NotFound" will be returned.

Closes-Bug: #1667794
Change-Id: I5610efa160864b0d91cd67961883a6bec5bb8dd0
This commit is contained in:
Sen Yang 2017-11-15 12:09:04 -06:00
parent 52b8e62d6e
commit 9213ec2d32
4 changed files with 289 additions and 36 deletions

View File

@ -633,6 +633,12 @@ class FakeSessionClient(base_client.SessionClient):
def post_servers_uuid4_metadata(self, **kw):
return (204, {}, {'metadata': {'key1': 'val1'}})
def post_servers_uuid5_metadata(self, **kw):
return (204, {}, {'metadata': {'key1': 'val1'}})
def post_servers_uuid6_metadata(self, **kw):
return (204, {}, {'metadata': {'key1': 'val1'}})
def delete_servers_uuid1_metadata_key1(self, **kw):
return (200, {}, {'data': 'Fake diagnostics'})
@ -645,6 +651,12 @@ class FakeSessionClient(base_client.SessionClient):
def delete_servers_uuid4_metadata_key1(self, **kw):
return (200, {}, {'data': 'Fake diagnostics'})
def delete_servers_uuid5_metadata_key1(self, **kw):
return (200, {}, {'data': 'Fake diagnostics'})
def delete_servers_uuid6_metadata_key1(self, **kw):
return (200, {}, {'data': 'Fake diagnostics'})
def get_servers_1234_os_security_groups(self, **kw):
return (200, {}, {
"security_groups": [{
@ -1773,6 +1785,26 @@ class FakeSessionClient(base_client.SessionClient):
{'name': 'inst4', 'uuid': 'uuid4'}]}]
})
def get_os_hypervisors_hyper1_servers(self, **kw):
return (200, {}, {
'hypervisors': [
{'id': 1234,
'hypervisor_hostname': 'hyper1',
'servers': [
{'name': 'inst1', 'uuid': 'uuid1'},
{'name': 'inst2', 'uuid': 'uuid2'}]}]
})
def get_os_hypervisors_hyper2_servers(self, **kw):
return (200, {}, {
'hypervisors': [
{'id': 5678,
'hypervisor_hostname': 'hyper2',
'servers': [
{'name': 'inst3', 'uuid': 'uuid3'},
{'name': 'inst4', 'uuid': 'uuid4'}]}]
})
def get_os_hypervisors_hyper_no_servers_servers(self, **kw):
return (200, {}, {'hypervisors':
[{'id': 1234, 'hypervisor_hostname': 'hyper1'}]})
@ -1986,6 +2018,12 @@ class FakeSessionClient(base_client.SessionClient):
def post_servers_uuid4_action(self, **kw):
return 202, {}, {}
def post_servers_uuid5_action(self, **kw):
return 202, {}, {}
def post_servers_uuid6_action(self, **kw):
return 202, {}, {}
def get_os_cells_child_cell(self, **kw):
cell = {'cell': {
'username': 'cell1_user',

View File

@ -1915,16 +1915,40 @@ class ShellTest(utils.TestCase):
{'metadata': {'key1': 'val1', 'key2': 'val2'}},
pos=4)
def test_set_host_meta_strict(self):
self.run_command('host-meta hyper1 --strict set key1=val1 key2=val2')
self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0)
self.assert_called('POST', '/servers/uuid1/metadata',
{'metadata': {'key1': 'val1', 'key2': 'val2'}},
pos=1)
self.assert_called('POST', '/servers/uuid2/metadata',
{'metadata': {'key1': 'val1', 'key2': 'val2'}},
pos=2)
def test_set_host_meta_no_match(self):
cmd = 'host-meta hyper --strict set key1=val1 key2=val2'
self.assertRaises(exceptions.NotFound, self.run_command, cmd)
def test_set_host_meta_with_no_servers(self):
self.run_command('host-meta hyper_no_servers set key1=val1 key2=val2')
self.assert_called('GET', '/os-hypervisors/hyper_no_servers/servers')
def test_set_host_meta_with_no_servers_strict(self):
cmd = 'host-meta hyper_no_servers --strict set key1=val1 key2=val2'
self.assertRaises(exceptions.NotFound, self.run_command, cmd)
def test_delete_host_meta(self):
self.run_command('host-meta hyper delete key1')
self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0)
self.assert_called('DELETE', '/servers/uuid1/metadata/key1', pos=1)
self.assert_called('DELETE', '/servers/uuid2/metadata/key1', pos=2)
def test_delete_host_meta_strict(self):
self.run_command('host-meta hyper1 --strict delete key1')
self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0)
self.assert_called('DELETE', '/servers/uuid1/metadata/key1', pos=1)
self.assert_called('DELETE', '/servers/uuid2/metadata/key1', pos=2)
def test_usage_list(self):
cmd = 'usage-list --start 2000-01-20 --end 2005-02-01'
stdout, _stderr = self.run_command(cmd)
@ -2300,6 +2324,19 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/uuid3/action', body, pos=3)
self.assert_called('POST', '/servers/uuid4/action', body, pos=4)
def test_host_evacuate_live_with_no_target_host_strict(self):
self.run_command('host-evacuate-live hyper1 --strict')
self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0)
body = {'os-migrateLive': {'host': None,
'block_migration': False,
'disk_over_commit': False}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=1)
self.assert_called('POST', '/servers/uuid2/action', body, pos=2)
def test_host_evacuate_live_no_match(self):
cmd = 'host-evacuate-live hyper --strict'
self.assertRaises(exceptions.NotFound, self.run_command, cmd)
def test_host_evacuate_live_2_25(self):
self.run_command('host-evacuate-live hyper', api_version='2.25')
self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0)
@ -2309,6 +2346,14 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/uuid3/action', body, pos=3)
self.assert_called('POST', '/servers/uuid4/action', body, pos=4)
def test_host_evacuate_live_2_25_strict(self):
self.run_command('host-evacuate-live hyper1 --strict',
api_version='2.25')
self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0)
body = {'os-migrateLive': {'host': None, 'block_migration': 'auto'}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=1)
self.assert_called('POST', '/servers/uuid2/action', body, pos=2)
def test_host_evacuate_live_with_target_host(self):
self.run_command('host-evacuate-live hyper '
'--target-host hostname')
@ -2321,6 +2366,16 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/uuid3/action', body, pos=3)
self.assert_called('POST', '/servers/uuid4/action', body, pos=4)
def test_host_evacuate_live_with_target_host_strict(self):
self.run_command('host-evacuate-live hyper1 '
'--target-host hostname --strict')
self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0)
body = {'os-migrateLive': {'host': 'hostname',
'block_migration': False,
'disk_over_commit': False}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=1)
self.assert_called('POST', '/servers/uuid2/action', body, pos=2)
def test_host_evacuate_live_2_30(self):
self.run_command('host-evacuate-live --force hyper '
'--target-host hostname',
@ -2334,6 +2389,17 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/uuid3/action', body, pos=3)
self.assert_called('POST', '/servers/uuid4/action', body, pos=4)
def test_host_evacuate_live_2_30_strict(self):
self.run_command('host-evacuate-live --force hyper1 '
'--target-host hostname --strict',
api_version='2.30')
self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0)
body = {'os-migrateLive': {'host': 'hostname',
'block_migration': 'auto',
'force': True}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=1)
self.assert_called('POST', '/servers/uuid2/action', body, pos=2)
def test_host_evacuate_live_with_block_migration(self):
self.run_command('host-evacuate-live --block-migrate hyper')
self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0)
@ -2345,6 +2411,15 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/uuid3/action', body, pos=3)
self.assert_called('POST', '/servers/uuid4/action', body, pos=4)
def test_host_evacuate_live_with_block_migration_strict(self):
self.run_command('host-evacuate-live --block-migrate hyper2 --strict')
self.assert_called('GET', '/os-hypervisors/hyper2/servers', pos=0)
body = {'os-migrateLive': {'host': None,
'block_migration': True,
'disk_over_commit': False}}
self.assert_called('POST', '/servers/uuid3/action', body, pos=1)
self.assert_called('POST', '/servers/uuid4/action', body, pos=2)
def test_host_evacuate_live_with_block_migration_2_25(self):
self.run_command('host-evacuate-live --block-migrate hyper',
api_version='2.25')
@ -2355,6 +2430,14 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/uuid3/action', body, pos=3)
self.assert_called('POST', '/servers/uuid4/action', body, pos=4)
def test_host_evacuate_live_with_block_migration_2_25_strict(self):
self.run_command('host-evacuate-live --block-migrate hyper2 --strict',
api_version='2.25')
self.assert_called('GET', '/os-hypervisors/hyper2/servers', pos=0)
body = {'os-migrateLive': {'host': None, 'block_migration': True}}
self.assert_called('POST', '/servers/uuid3/action', body, pos=1)
self.assert_called('POST', '/servers/uuid4/action', body, pos=2)
def test_host_evacuate_live_with_disk_over_commit(self):
self.run_command('host-evacuate-live --disk-over-commit hyper')
self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0)
@ -2366,11 +2449,26 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/uuid3/action', body, pos=3)
self.assert_called('POST', '/servers/uuid4/action', body, pos=4)
def test_host_evacuate_live_with_disk_over_commit_strict(self):
self.run_command('host-evacuate-live --disk-over-commit hyper2 '
'--strict')
self.assert_called('GET', '/os-hypervisors/hyper2/servers', pos=0)
body = {'os-migrateLive': {'host': None,
'block_migration': False,
'disk_over_commit': True}}
self.assert_called('POST', '/servers/uuid3/action', body, pos=1)
self.assert_called('POST', '/servers/uuid4/action', body, pos=2)
def test_host_evacuate_live_with_disk_over_commit_2_25(self):
self.assertRaises(SystemExit, self.run_command,
'host-evacuate-live --disk-over-commit hyper',
api_version='2.25')
def test_host_evacuate_live_with_disk_over_commit_2_25_strict(self):
self.assertRaises(SystemExit, self.run_command,
'host-evacuate-live --disk-over-commit hyper2 '
'--strict', api_version='2.25')
def test_host_evacuate_list_with_max_servers(self):
self.run_command('host-evacuate-live --max-servers 1 hyper')
self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0)
@ -2379,6 +2477,14 @@ class ShellTest(utils.TestCase):
'disk_over_commit': False}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=1)
def test_host_evacuate_list_with_max_servers_strict(self):
self.run_command('host-evacuate-live --max-servers 1 hyper1 --strict')
self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0)
body = {'os-migrateLive': {'host': None,
'block_migration': False,
'disk_over_commit': False}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=1)
def test_reset_state(self):
self.run_command('reset-state sample-server')
self.assert_called('POST', '/servers/1234/action',
@ -2506,6 +2612,15 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/uuid4/action',
{'evacuate': {'host': 'target_hyper'}}, pos=4)
def test_host_evacuate_v2_14_strict(self):
self.run_command('host-evacuate hyper1 --target target_hyper --strict',
api_version='2.14')
self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0)
self.assert_called('POST', '/servers/uuid1/action',
{'evacuate': {'host': 'target_hyper'}}, pos=1)
self.assert_called('POST', '/servers/uuid2/action',
{'evacuate': {'host': 'target_hyper'}}, pos=2)
def test_host_evacuate(self):
self.run_command('host-evacuate hyper --target target_hyper')
self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0)
@ -2522,6 +2637,20 @@ class ShellTest(utils.TestCase):
{'evacuate': {'host': 'target_hyper',
'onSharedStorage': False}}, pos=4)
def test_host_evacuate_strict(self):
self.run_command('host-evacuate hyper1 --target target_hyper --strict')
self.assert_called('GET', '/os-hypervisors/hyper1/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)
def test_host_evacuate_no_match(self):
cmd = 'host-evacuate hyper --target target_hyper --strict'
self.assertRaises(exceptions.NotFound, self.run_command, cmd)
def test_host_evacuate_v2_29(self):
self.run_command('host-evacuate hyper --target target_hyper --force',
api_version='2.29')
@ -2539,6 +2668,17 @@ class ShellTest(utils.TestCase):
{'evacuate': {'host': 'target_hyper', 'force': True}
}, pos=4)
def test_host_evacuate_v2_29_strict(self):
self.run_command('host-evacuate hyper1 --target target_hyper'
' --force --strict', api_version='2.29')
self.assert_called('GET', '/os-hypervisors/hyper1/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)
def test_host_evacuate_with_shared_storage(self):
self.run_command(
'host-evacuate --on-shared-storage hyper --target target_hyper')
@ -2556,6 +2696,17 @@ class ShellTest(utils.TestCase):
{'evacuate': {'host': 'target_hyper',
'onSharedStorage': True}}, pos=4)
def test_host_evacuate_with_shared_storage_strict(self):
self.run_command('host-evacuate --on-shared-storage hyper1'
' --target target_hyper --strict')
self.assert_called('GET', '/os-hypervisors/hyper1/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)
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)
@ -2568,6 +2719,14 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/uuid4/action',
{'evacuate': {'onSharedStorage': True}}, pos=4)
def test_host_evacuate_with_no_target_host_strict(self):
self.run_command('host-evacuate --on-shared-storage hyper1 --strict')
self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0)
self.assert_called('POST', '/servers/uuid1/action',
{'evacuate': {'onSharedStorage': True}}, pos=1)
self.assert_called('POST', '/servers/uuid2/action',
{'evacuate': {'onSharedStorage': True}}, pos=2)
def test_host_servers_migrate(self):
self.run_command('host-servers-migrate hyper')
self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0)
@ -2580,6 +2739,18 @@ class ShellTest(utils.TestCase):
self.assert_called('POST',
'/servers/uuid4/action', {'migrate': None}, pos=4)
def test_host_servers_migrate_strict(self):
self.run_command('host-servers-migrate hyper1 --strict')
self.assert_called('GET', '/os-hypervisors/hyper1/servers', pos=0)
self.assert_called('POST',
'/servers/uuid1/action', {'migrate': None}, pos=1)
self.assert_called('POST',
'/servers/uuid2/action', {'migrate': None}, pos=2)
def test_host_servers_migrate_no_match(self):
cmd = 'host-servers-migrate hyper --strict'
self.assertRaises(exceptions.NotFound, self.run_command, cmd)
def test_hypervisor_list(self):
self.run_command('hypervisor-list')
self.assert_called('GET', '/os-hypervisors')

View File

@ -4674,6 +4674,23 @@ def _server_evacuate(cs, server, args):
"error_message": error_message})
def _hyper_servers(cs, host, strict):
hypervisors = cs.hypervisors.search(host, servers=True)
for hyper in hypervisors:
if strict and hyper.hypervisor_hostname != host:
continue
if hasattr(hyper, 'servers'):
for server in hyper.servers:
yield server
if strict:
break
else:
if strict:
msg = (_("No hypervisor matching '%s' could be found.") %
host)
raise exceptions.NotFound(404, msg)
@utils.arg('host', metavar='<host>',
help='The hypervisor hostname (or pattern) to search for. '
'WARNING: Use a fully qualified domain name if you only '
@ -4699,18 +4716,22 @@ def _server_evacuate(cs, server, args):
default=False,
help=_('Force to not verify the scheduler if a host is provided.'),
start_version='2.29')
@utils.arg(
'--strict',
dest='strict',
action='store_true',
default=False,
help=_('Evacuate host with exact hypervisor hostname match'))
def do_host_evacuate(cs, args):
"""Evacuate all instances from failed host."""
hypervisors = cs.hypervisors.search(args.host, servers=True)
response = []
for hyper in hypervisors:
if hasattr(hyper, 'servers'):
for server in hyper.servers:
for server in _hyper_servers(cs, args.host, args.strict):
response.append(_server_evacuate(cs, server, args))
utils.print_list(response,
["Server UUID", "Evacuate Accepted", "Error Message"])
utils.print_list(response, [
"Server UUID",
"Evacuate Accepted",
"Error Message",
])
def _server_live_migrate(cs, server, args):
@ -4780,22 +4801,29 @@ def _server_live_migrate(cs, server, args):
default=False,
help=_('Force to not verify the scheduler if a host is provided.'),
start_version='2.30')
@utils.arg(
'--strict',
dest='strict',
action='store_true',
default=False,
help=_('live Evacuate host with exact hypervisor hostname match'))
def do_host_evacuate_live(cs, args):
"""Live migrate all instances of the specified host
to other available hosts.
"""
hypervisors = cs.hypervisors.search(args.host, servers=True)
response = []
migrating = 0
for hyper in hypervisors:
for server in getattr(hyper, 'servers', []):
for server in _hyper_servers(cs, args.host, args.strict):
response.append(_server_live_migrate(cs, server, args))
migrating += 1
if args.max_servers is not None and migrating >= args.max_servers:
migrating = migrating + 1
if (args.max_servers is not None and
migrating >= args.max_servers):
break
utils.print_list(response, ["Server UUID", "Live Migration Accepted",
"Error Message"])
utils.print_list(response, [
"Server UUID",
"Live Migration Accepted",
"Error Message",
])
class HostServersMigrateResponse(base.Resource):
@ -4820,20 +4848,24 @@ def _server_migrate(cs, server):
help='The hypervisor hostname (or pattern) to search for. '
'WARNING: Use a fully qualified domain name if you only '
'want to cold migrate from a specific host.')
@utils.arg(
'--strict',
dest='strict',
action='store_true',
default=False,
help=_('Migrate host with exact hypervisor hostname match'))
def do_host_servers_migrate(cs, args):
"""Cold migrate all instances off the specified host to other available
hosts.
"""
hypervisors = cs.hypervisors.search(args.host, servers=True)
response = []
for hyper in hypervisors:
if hasattr(hyper, 'servers'):
for server in hyper.servers:
for server in _hyper_servers(cs, args.host, args.strict):
response.append(_server_migrate(cs, server))
utils.print_list(response,
["Server UUID", "Migration Accepted", "Error Message"])
utils.print_list(response, [
"Server UUID",
"Migration Accepted",
"Error Message",
])
@utils.arg(
@ -4963,13 +4995,16 @@ def do_list_extensions(cs, _args):
action='append',
default=[],
help=_('Metadata to set or delete (only key is necessary on delete)'))
@utils.arg(
'--strict',
dest='strict',
action='store_true',
default=False,
help=_('Set host-meta to the hypervisor with exact hostname match'))
def do_host_meta(cs, args):
"""Set or Delete metadata on all instances of a host."""
hypervisors = cs.hypervisors.search(args.host, servers=True)
for hyper in hypervisors:
for server in _hyper_servers(cs, args.host, args.strict):
metadata = _extract_metadata(args)
if hasattr(hyper, 'servers'):
for server in hyper.servers:
if args.action == 'set':
cs.servers.set_meta(server['uuid'], metadata)
elif args.action == 'delete':

View File

@ -0,0 +1,9 @@
---
features:
- |
Provides "--strict" option for "nova host-servers-migrate", "nova host-evacuate",
"nova host-evacuate-live" and "nova host-meta" commands. When "--strict" option is
used, the action will be applied to a single compute with the exact hypervisor
hostname string match rather than to the computes with hostname substring match.
When the specified hostname does not exist in the system, "NotFound" error code
will be returned.