Live migrate each instance from one host to other hosts

Using the existing server live_migrate api, this new feature,
adds the ability for admins to live-migrate all running instances
from one host to other hosts. The patch implements the feature
for Nova API V2 and API V3.

Co-Authored-By: Cédric Soulas <cedric.soulas@cloudwatt.com>

Change-Id: Ie8dd1b66fb8eaefa6ff38752b1e4f46bab145820
Implements: blueprint host-servers-live-migrate
This commit is contained in:
Ala Rezmerita 2014-04-24 11:03:32 +02:00
parent 2a1c07e790
commit a400b58d03
5 changed files with 201 additions and 0 deletions

View File

@ -1389,6 +1389,51 @@ class ShellTest(utils.TestCase):
'block_migration': True,
'disk_over_commit': True}})
def test_host_evacuate_live_with_no_target_host(self):
self.run_command('host-evacuate-live hyper')
self.assert_called('GET', '/os-hypervisors/hyper/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)
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(self):
self.run_command('host-evacuate-live hyper '
'--target-host hostname')
self.assert_called('GET', '/os-hypervisors/hyper/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)
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(self):
self.run_command('host-evacuate-live --block-migrate hyper')
self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0)
body = {'os-migrateLive': {'host': None,
'block_migration': True,
'disk_over_commit': False}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=1)
self.assert_called('POST', '/servers/uuid2/action', body, pos=2)
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(self):
self.run_command('host-evacuate-live --disk-over-commit hyper')
self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0)
body = {'os-migrateLive': {'host': None,
'block_migration': False,
'disk_over_commit': True}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=1)
self.assert_called('POST', '/servers/uuid2/action', body, pos=2)
self.assert_called('POST', '/servers/uuid3/action', body, pos=3)
self.assert_called('POST', '/servers/uuid4/action', body, pos=4)
def test_reset_state(self):
self.run_command('reset-state sample-server')
self.assert_called('POST', '/servers/1234/action',

View File

@ -335,6 +335,10 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
# Hypervisors
#
def get_os_hypervisors_search(self, **kw):
if kw['query'] == 'hyper1':
return (200, {}, {'hypervisors': [
{'id': 1234, 'hypervisor_hostname': 'hyper1'},
]})
return (200, {}, {'hypervisors': [
{'id': 1234, 'hypervisor_hostname': 'hyper1'},
{'id': 5678, 'hypervisor_hostname': 'hyper2'}

View File

@ -644,6 +644,47 @@ class ShellTest(utils.TestCase):
self.assert_called('GET', '/flavors/2', pos=3)
self.assert_called('GET', '/flavors/2/flavor-extra-specs', pos=4)
def test_host_evacuate_live_with_no_target_host(self):
self.run_command('host-evacuate-live hyper1')
self.assert_called('GET', '/os-hypervisors/search?query=hyper1', pos=0)
self.assert_called('GET', '/os-hypervisors/1234/servers', pos=1)
body = {'migrate_live': {'host': None,
'block_migration': False,
'disk_over_commit': False}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=2)
self.assert_called('POST', '/servers/uuid2/action', body, pos=3)
def test_host_evacuate_live_with_target_host(self):
self.run_command('host-evacuate-live hyper1 '
'--target-host hostname')
self.assert_called('GET', '/os-hypervisors/search?query=hyper1', pos=0)
self.assert_called('GET', '/os-hypervisors/1234/servers', pos=1)
body = {'migrate_live': {'host': 'hostname',
'block_migration': False,
'disk_over_commit': False}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=2)
self.assert_called('POST', '/servers/uuid2/action', body, pos=3)
def test_host_evacuate_live_with_block_migration(self):
self.run_command('host-evacuate-live --block-migrate hyper1')
self.assert_called('GET', '/os-hypervisors/search?query=hyper1', pos=0)
self.assert_called('GET', '/os-hypervisors/1234/servers', pos=1)
body = {'migrate_live': {'host': None,
'block_migration': True,
'disk_over_commit': False}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=2)
self.assert_called('POST', '/servers/uuid2/action', body, pos=3)
def test_host_evacuate_live_with_disk_over_commit(self):
self.run_command('host-evacuate-live --disk-over-commit hyper1')
self.assert_called('GET', '/os-hypervisors/search?query=hyper1', pos=0)
self.assert_called('GET', '/os-hypervisors/1234/servers', pos=1)
body = {'migrate_live': {'host': None,
'block_migration': False,
'disk_over_commit': True}}
self.assert_called('POST', '/servers/uuid1/action', body, pos=2)
self.assert_called('POST', '/servers/uuid2/action', body, pos=3)
def test_delete(self):
self.run_command('delete 1234')
self.assert_called('DELETE', '/servers/1234')

View File

@ -0,0 +1,64 @@
# Copyright 2014 OpenStack Foundation
# 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.openstack.common.gettextutils import _
from novaclient import utils
def _server_live_migrate(cs, server, args):
class HostEvacuateLiveResponse(object):
def __init__(self, server_uuid, live_migration_accepted,
error_message):
self.server_uuid = server_uuid
self.live_migration_accepted = live_migration_accepted
self.error_message = error_message
success = True
error_message = ""
try:
cs.servers.live_migrate(server['uuid'], args.target_host,
args.block_migrate, args.disk_over_commit)
except Exception as e:
success = False
error_message = _("Error while live migrating instance: %s") % e
return HostEvacuateLiveResponse(server['uuid'],
success,
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('--block-migrate',
action='store_true',
default=False,
help=_('Enable block migration.'))
@utils.arg('--disk-over-commit',
action='store_true',
default=False,
help=_('Enable disk overcommit.'))
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 = []
for hyper in hypervisors:
for server in getattr(hyper, 'servers', []):
response.append(_server_live_migrate(cs, server, args))
utils.print_list(response, ["Server UUID", "Live Migration Accepted",
"Error Message"])

View File

@ -2392,6 +2392,53 @@ def do_live_migration(cs, args):
args.disk_over_commit)
def _server_live_migrate(cs, server, args):
class HostServersLiveMigrateResponse(object):
def __init__(self, server_uuid, live_migration_accepted,
error_message):
self.server_uuid = server_uuid
self.live_migration_accepted = live_migration_accepted
self.error_message = error_message
success = True
error_message = ""
try:
cs.servers.live_migrate(server['id'], args.target_host,
args.block_migrate, args.disk_over_commit)
except Exception as e:
success = False
error_message = "Error while live migrating instance: %s" % e
return HostServersLiveMigrateResponse(server['id'],
success,
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('--block-migrate',
action='store_true',
default=False,
help='Enable block migration.')
@utils.arg('--disk-over-commit',
action='store_true',
default=False,
help='Enable disk overcommit.')
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)
response = []
for hyper in hypervisors:
servers = getattr(cs.hypervisors.servers(hyper.id), 'servers', [])
for server in servers:
response.append(_server_live_migrate(cs, server, args))
utils.print_list(response, ["Server UUID", "Live Migration Accepted",
"Error Message"])
@utils.arg('server', metavar='<server>', nargs='+',
help='Name or ID of server(s).')
@utils.arg('--active', action='store_const', dest='state',