Adding kvm-block-migration feature.
I wrote some description the below URL. I hope it may help for reviewing. <http://etherpad.openstack.org/kvm-block-migration>
This commit is contained in:
1
Authors
1
Authors
@@ -59,6 +59,7 @@ Joshua McKenty <jmckenty@gmail.com>
|
|||||||
Justin Santa Barbara <justin@fathomdb.com>
|
Justin Santa Barbara <justin@fathomdb.com>
|
||||||
Justin Shepherd <jshepher@rackspace.com>
|
Justin Shepherd <jshepher@rackspace.com>
|
||||||
Kei Masumoto <masumotok@nttdata.co.jp>
|
Kei Masumoto <masumotok@nttdata.co.jp>
|
||||||
|
masumoto<masumotok@nttdata.co.jp>
|
||||||
Ken Pepple <ken.pepple@gmail.com>
|
Ken Pepple <ken.pepple@gmail.com>
|
||||||
Kevin Bringard <kbringard@attinteractive.com>
|
Kevin Bringard <kbringard@attinteractive.com>
|
||||||
Kevin L. Mitchell <kevin.mitchell@rackspace.com>
|
Kevin L. Mitchell <kevin.mitchell@rackspace.com>
|
||||||
|
|||||||
@@ -834,11 +834,13 @@ class VmCommands(object):
|
|||||||
instance['availability_zone'],
|
instance['availability_zone'],
|
||||||
instance['launch_index'])
|
instance['launch_index'])
|
||||||
|
|
||||||
@args('--ec2_id', dest='ec2_id', metavar='<ec2 id>', help='EC2 ID')
|
def _migration(self, ec2_id, dest, block_migration=False):
|
||||||
@args('--dest', dest='dest', metavar='<Destanation>',
|
"""Migrates a running instance to a new machine.
|
||||||
help='destanation node')
|
:param ec2_id: instance id which comes from euca-describe-instance.
|
||||||
def live_migration(self, ec2_id, dest):
|
:param dest: destination host name.
|
||||||
"""Migrates a running instance to a new machine."""
|
:param block_migration: if True, do block_migration.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
ctxt = context.get_admin_context()
|
ctxt = context.get_admin_context()
|
||||||
instance_id = ec2utils.ec2_id_to_id(ec2_id)
|
instance_id = ec2utils.ec2_id_to_id(ec2_id)
|
||||||
@@ -859,11 +861,28 @@ class VmCommands(object):
|
|||||||
{"method": "live_migration",
|
{"method": "live_migration",
|
||||||
"args": {"instance_id": instance_id,
|
"args": {"instance_id": instance_id,
|
||||||
"dest": dest,
|
"dest": dest,
|
||||||
"topic": FLAGS.compute_topic}})
|
"topic": FLAGS.compute_topic,
|
||||||
|
"block_migration": block_migration}})
|
||||||
|
|
||||||
print _('Migration of %s initiated.'
|
print _('Migration of %s initiated.'
|
||||||
'Check its progress using euca-describe-instances.') % ec2_id
|
'Check its progress using euca-describe-instances.') % ec2_id
|
||||||
|
|
||||||
|
@args('--ec2_id', dest='ec2_id', metavar='<ec2 id>', help='EC2 ID')
|
||||||
|
@args('--dest', dest='dest', metavar='<Destanation>',
|
||||||
|
help='destanation node')
|
||||||
|
def live_migration(self, ec2_id, dest):
|
||||||
|
"""Migrates a running instance to a new machine."""
|
||||||
|
|
||||||
|
self._migration(ec2_id, dest)
|
||||||
|
|
||||||
|
@args('--ec2_id', dest='ec2_id', metavar='<ec2 id>', help='EC2 ID')
|
||||||
|
@args('--dest', dest='dest', metavar='<Destanation>',
|
||||||
|
help='destanation node')
|
||||||
|
def block_migration(self, ec2_id, dest):
|
||||||
|
"""Migrates a running instance to a new machine with storage data."""
|
||||||
|
|
||||||
|
self._migration(ec2_id, dest, True)
|
||||||
|
|
||||||
|
|
||||||
class ServiceCommands(object):
|
class ServiceCommands(object):
|
||||||
"""Enable and disable running services"""
|
"""Enable and disable running services"""
|
||||||
@@ -945,9 +964,19 @@ class ServiceCommands(object):
|
|||||||
mem_u = result['resource']['memory_mb_used']
|
mem_u = result['resource']['memory_mb_used']
|
||||||
hdd_u = result['resource']['local_gb_used']
|
hdd_u = result['resource']['local_gb_used']
|
||||||
|
|
||||||
|
cpu_sum = 0
|
||||||
|
mem_sum = 0
|
||||||
|
hdd_sum = 0
|
||||||
print 'HOST\t\t\tPROJECT\t\tcpu\tmem(mb)\tdisk(gb)'
|
print 'HOST\t\t\tPROJECT\t\tcpu\tmem(mb)\tdisk(gb)'
|
||||||
print '%s(total)\t\t\t%s\t%s\t%s' % (host, cpu, mem, hdd)
|
print '%s(total)\t\t\t%s\t%s\t%s' % (host, cpu, mem, hdd)
|
||||||
print '%s(used)\t\t\t%s\t%s\t%s' % (host, cpu_u, mem_u, hdd_u)
|
print '%s(used_now)\t\t\t%s\t%s\t%s' % (host, cpu_u, mem_u, hdd_u)
|
||||||
|
for p_id, val in result['usage'].items():
|
||||||
|
cpu_sum += val['vcpus']
|
||||||
|
mem_sum += val['memory_mb']
|
||||||
|
hdd_sum += val['local_gb']
|
||||||
|
print '%s(used_max)\t\t\t%s\t%s\t%s' % (host, cpu_sum,
|
||||||
|
mem_sum, hdd_sum)
|
||||||
|
|
||||||
for p_id, val in result['usage'].items():
|
for p_id, val in result['usage'].items():
|
||||||
print '%s\t\t%s\t\t%s\t%s\t%s' % (host,
|
print '%s\t\t%s\t\t%s\t%s\t%s' % (host,
|
||||||
p_id,
|
p_id,
|
||||||
|
|||||||
@@ -714,11 +714,15 @@ class ComputeTestCase(test.TestCase):
|
|||||||
dbmock.queue_get_for(c, FLAGS.compute_topic, i_ref['host']).\
|
dbmock.queue_get_for(c, FLAGS.compute_topic, i_ref['host']).\
|
||||||
AndReturn(topic)
|
AndReturn(topic)
|
||||||
rpc.call(c, topic, {"method": "pre_live_migration",
|
rpc.call(c, topic, {"method": "pre_live_migration",
|
||||||
"args": {'instance_id': i_ref['id']}})
|
"args": {'instance_id': i_ref['id'],
|
||||||
|
'block_migration': False,
|
||||||
|
'disk': None}})
|
||||||
|
|
||||||
self.mox.StubOutWithMock(self.compute.driver, 'live_migration')
|
self.mox.StubOutWithMock(self.compute.driver, 'live_migration')
|
||||||
self.compute.driver.live_migration(c, i_ref, i_ref['host'],
|
self.compute.driver.live_migration(c, i_ref, i_ref['host'],
|
||||||
self.compute.post_live_migration,
|
self.compute.post_live_migration,
|
||||||
self.compute.recover_live_migration)
|
self.compute.rollback_live_migration,
|
||||||
|
False)
|
||||||
|
|
||||||
self.compute.db = dbmock
|
self.compute.db = dbmock
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
@@ -739,13 +743,18 @@ class ComputeTestCase(test.TestCase):
|
|||||||
dbmock.queue_get_for(c, FLAGS.compute_topic, i_ref['host']).\
|
dbmock.queue_get_for(c, FLAGS.compute_topic, i_ref['host']).\
|
||||||
AndReturn(topic)
|
AndReturn(topic)
|
||||||
rpc.call(c, topic, {"method": "pre_live_migration",
|
rpc.call(c, topic, {"method": "pre_live_migration",
|
||||||
"args": {'instance_id': i_ref['id']}}).\
|
"args": {'instance_id': i_ref['id'],
|
||||||
|
'block_migration': False,
|
||||||
|
'disk': None}}).\
|
||||||
AndRaise(rpc.RemoteError('', '', ''))
|
AndRaise(rpc.RemoteError('', '', ''))
|
||||||
dbmock.instance_update(c, i_ref['id'], {'state_description': 'running',
|
dbmock.instance_update(c, i_ref['id'], {'state_description': 'running',
|
||||||
'state': power_state.RUNNING,
|
'state': power_state.RUNNING,
|
||||||
'host': i_ref['host']})
|
'host': i_ref['host']})
|
||||||
for v in i_ref['volumes']:
|
for v in i_ref['volumes']:
|
||||||
dbmock.volume_update(c, v['id'], {'status': 'in-use'})
|
dbmock.volume_update(c, v['id'], {'status': 'in-use'})
|
||||||
|
# mock for volume_api.remove_from_compute
|
||||||
|
rpc.call(c, topic, {"method": "remove_volume",
|
||||||
|
"args": {'volume_id': v['id']}})
|
||||||
|
|
||||||
self.compute.db = dbmock
|
self.compute.db = dbmock
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
@@ -766,7 +775,9 @@ class ComputeTestCase(test.TestCase):
|
|||||||
AndReturn(topic)
|
AndReturn(topic)
|
||||||
self.mox.StubOutWithMock(rpc, 'call')
|
self.mox.StubOutWithMock(rpc, 'call')
|
||||||
rpc.call(c, topic, {"method": "pre_live_migration",
|
rpc.call(c, topic, {"method": "pre_live_migration",
|
||||||
"args": {'instance_id': i_ref['id']}}).\
|
"args": {'instance_id': i_ref['id'],
|
||||||
|
'block_migration': False,
|
||||||
|
'disk': None}}).\
|
||||||
AndRaise(rpc.RemoteError('', '', ''))
|
AndRaise(rpc.RemoteError('', '', ''))
|
||||||
dbmock.instance_update(c, i_ref['id'], {'state_description': 'running',
|
dbmock.instance_update(c, i_ref['id'], {'state_description': 'running',
|
||||||
'state': power_state.RUNNING,
|
'state': power_state.RUNNING,
|
||||||
@@ -791,11 +802,14 @@ class ComputeTestCase(test.TestCase):
|
|||||||
dbmock.queue_get_for(c, FLAGS.compute_topic, i_ref['host']).\
|
dbmock.queue_get_for(c, FLAGS.compute_topic, i_ref['host']).\
|
||||||
AndReturn(topic)
|
AndReturn(topic)
|
||||||
rpc.call(c, topic, {"method": "pre_live_migration",
|
rpc.call(c, topic, {"method": "pre_live_migration",
|
||||||
"args": {'instance_id': i_ref['id']}})
|
"args": {'instance_id': i_ref['id'],
|
||||||
|
'block_migration': False,
|
||||||
|
'disk': None}})
|
||||||
self.mox.StubOutWithMock(self.compute.driver, 'live_migration')
|
self.mox.StubOutWithMock(self.compute.driver, 'live_migration')
|
||||||
self.compute.driver.live_migration(c, i_ref, i_ref['host'],
|
self.compute.driver.live_migration(c, i_ref, i_ref['host'],
|
||||||
self.compute.post_live_migration,
|
self.compute.post_live_migration,
|
||||||
self.compute.recover_live_migration)
|
self.compute.rollback_live_migration,
|
||||||
|
False)
|
||||||
|
|
||||||
self.compute.db = dbmock
|
self.compute.db = dbmock
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
@@ -829,6 +843,10 @@ class ComputeTestCase(test.TestCase):
|
|||||||
self.compute.volume_manager.remove_compute_volume(c, v['id'])
|
self.compute.volume_manager.remove_compute_volume(c, v['id'])
|
||||||
self.mox.StubOutWithMock(self.compute.driver, 'unfilter_instance')
|
self.mox.StubOutWithMock(self.compute.driver, 'unfilter_instance')
|
||||||
self.compute.driver.unfilter_instance(i_ref, [])
|
self.compute.driver.unfilter_instance(i_ref, [])
|
||||||
|
self.mox.StubOutWithMock(rpc, 'call')
|
||||||
|
rpc.call(c, db.queue_get_for(c, FLAGS.compute_topic, dest),
|
||||||
|
{"method": "post_live_migration_at_destination",
|
||||||
|
"args": {'instance_id': i_ref['id'], 'block_migration': False}})
|
||||||
|
|
||||||
# executing
|
# executing
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from xml.etree.ElementTree import fromstring as xml_to_tree
|
from xml.etree.ElementTree import fromstring as xml_to_tree
|
||||||
from xml.dom.minidom import parseString as xml_to_dom
|
from xml.dom.minidom import parseString as xml_to_dom
|
||||||
@@ -696,17 +697,20 @@ class LibvirtConnTestCase(test.TestCase):
|
|||||||
return vdmock
|
return vdmock
|
||||||
|
|
||||||
self.create_fake_libvirt_mock(lookupByName=fake_lookup)
|
self.create_fake_libvirt_mock(lookupByName=fake_lookup)
|
||||||
self.mox.StubOutWithMock(self.compute, "recover_live_migration")
|
# self.mox.StubOutWithMock(self.compute, "recover_live_migration")
|
||||||
self.compute.recover_live_migration(self.context, instance_ref,
|
self.mox.StubOutWithMock(self.compute, "rollback_live_migration")
|
||||||
dest='dest')
|
# self.compute.recover_live_migration(self.context, instance_ref,
|
||||||
|
# dest='dest')
|
||||||
|
self.compute.rollback_live_migration(self.context, instance_ref,
|
||||||
|
'dest', False)
|
||||||
|
|
||||||
# Start test
|
#start test
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
conn = connection.LibvirtConnection(False)
|
conn = connection.LibvirtConnection(False)
|
||||||
self.assertRaises(libvirt.libvirtError,
|
self.assertRaises(libvirt.libvirtError,
|
||||||
conn._live_migration,
|
conn._live_migration,
|
||||||
self.context, instance_ref, 'dest', '',
|
self.context, instance_ref, 'dest', False,
|
||||||
self.compute.recover_live_migration)
|
self.compute.rollback_live_migration)
|
||||||
|
|
||||||
instance_ref = db.instance_get(self.context, instance_ref['id'])
|
instance_ref = db.instance_get(self.context, instance_ref['id'])
|
||||||
self.assertTrue(instance_ref['state_description'] == 'running')
|
self.assertTrue(instance_ref['state_description'] == 'running')
|
||||||
@@ -717,6 +721,95 @@ class LibvirtConnTestCase(test.TestCase):
|
|||||||
db.volume_destroy(self.context, volume_ref['id'])
|
db.volume_destroy(self.context, volume_ref['id'])
|
||||||
db.instance_destroy(self.context, instance_ref['id'])
|
db.instance_destroy(self.context, instance_ref['id'])
|
||||||
|
|
||||||
|
def test_pre_block_migration_works_correctly(self):
|
||||||
|
"""Confirms pre_block_migration works correctly."""
|
||||||
|
|
||||||
|
# Skip if non-libvirt environment
|
||||||
|
if not self.lazy_load_library_exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Replace instances_path since this testcase creates tmpfile
|
||||||
|
tmpdir = tempfile.mkdtemp()
|
||||||
|
store = FLAGS.instances_path
|
||||||
|
FLAGS.instances_path = tmpdir
|
||||||
|
|
||||||
|
# Test data
|
||||||
|
instance_ref = db.instance_create(self.context, self.test_instance)
|
||||||
|
dummyjson = '[{"path": "%s/disk", "local_gb": "10G", "type": "raw"}]'
|
||||||
|
|
||||||
|
# Preparing mocks
|
||||||
|
# qemu-img should be mockd since test environment might not have
|
||||||
|
# large disk space.
|
||||||
|
self.mox.StubOutWithMock(utils, "execute")
|
||||||
|
utils.execute('sudo', 'qemu-img', 'create', '-f', 'raw',
|
||||||
|
'%s/%s/disk' % (tmpdir, instance_ref.name), '10G')
|
||||||
|
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
conn = connection.LibvirtConnection(False)
|
||||||
|
conn.pre_block_migration(self.context, instance_ref,
|
||||||
|
dummyjson % tmpdir)
|
||||||
|
|
||||||
|
self.assertTrue(os.path.exists('%s/%s/' %
|
||||||
|
(tmpdir, instance_ref.name)))
|
||||||
|
|
||||||
|
shutil.rmtree(tmpdir)
|
||||||
|
db.instance_destroy(self.context, instance_ref['id'])
|
||||||
|
# Restore FLAGS.instances_path
|
||||||
|
FLAGS.instances_path = store
|
||||||
|
|
||||||
|
def test_get_instance_disk_info_works_correctly(self):
|
||||||
|
"""Confirms pre_block_migration works correctly."""
|
||||||
|
# Skip if non-libvirt environment
|
||||||
|
if not self.lazy_load_library_exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test data
|
||||||
|
instance_ref = db.instance_create(self.context, self.test_instance)
|
||||||
|
dummyxml = ("<domain type='kvm'><name>instance-0000000a</name>"
|
||||||
|
"<devices>"
|
||||||
|
"<disk type='file'><driver name='qemu' type='raw'/>"
|
||||||
|
"<source file='/test/disk'/>"
|
||||||
|
"<target dev='vda' bus='virtio'/></disk>"
|
||||||
|
"<disk type='file'><driver name='qemu' type='qcow2'/>"
|
||||||
|
"<source file='/test/disk.local'/>"
|
||||||
|
"<target dev='vdb' bus='virtio'/></disk>"
|
||||||
|
"</devices></domain>")
|
||||||
|
|
||||||
|
ret = ("image: /test/disk\nfile format: raw\n"
|
||||||
|
"virtual size: 20G (21474836480 bytes)\ndisk size: 3.1G\n")
|
||||||
|
|
||||||
|
# Preparing mocks
|
||||||
|
vdmock = self.mox.CreateMock(libvirt.virDomain)
|
||||||
|
self.mox.StubOutWithMock(vdmock, "XMLDesc")
|
||||||
|
vdmock.XMLDesc(0).AndReturn(dummyxml)
|
||||||
|
|
||||||
|
def fake_lookup(instance_name):
|
||||||
|
if instance_name == instance_ref.name:
|
||||||
|
return vdmock
|
||||||
|
self.create_fake_libvirt_mock(lookupByName=fake_lookup)
|
||||||
|
|
||||||
|
self.mox.StubOutWithMock(os.path, "getsize")
|
||||||
|
# based on above testdata, one is raw image, so getsize is mocked.
|
||||||
|
os.path.getsize("/test/disk").AndReturn(10 * 1024 * 1024 * 1024)
|
||||||
|
# another is qcow image, so qemu-img should be mocked.
|
||||||
|
self.mox.StubOutWithMock(utils, "execute")
|
||||||
|
utils.execute('sudo', 'qemu-img', 'info', '/test/disk.local').\
|
||||||
|
AndReturn((ret, ''))
|
||||||
|
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
conn = connection.LibvirtConnection(False)
|
||||||
|
info = conn.get_instance_disk_info(self.context, instance_ref)
|
||||||
|
info = utils.loads(info)
|
||||||
|
|
||||||
|
self.assertTrue(info[0]['type'] == 'raw' and
|
||||||
|
info[1]['type'] == 'qcow2' and
|
||||||
|
info[0]['path'] == '/test/disk' and
|
||||||
|
info[1]['path'] == '/test/disk.local' and
|
||||||
|
info[0]['local_gb'] == '10G' and
|
||||||
|
info[1]['local_gb'] == '20G')
|
||||||
|
|
||||||
|
db.instance_destroy(self.context, instance_ref['id'])
|
||||||
|
|
||||||
def test_spawn_with_network_info(self):
|
def test_spawn_with_network_info(self):
|
||||||
# Skip if non-libvirt environment
|
# Skip if non-libvirt environment
|
||||||
if not self.lazy_load_library_exists():
|
if not self.lazy_load_library_exists():
|
||||||
|
|||||||
Reference in New Issue
Block a user