[PATCH 0/3 v3] Live migration backend

From: Daniel Henrique Barboza <dhbarboza82@gmail.com> Changes in v3: - get hostname by using python socket API instead of run_command with 'hostname' command - closed remote connection in _migrate_task - removed 'test:///default' declaration in test files - added a test to cover the API Changes in v2: - changes asked by pvital: verifying host name properly, break line in docs/API.md, changed the format of i18n messages. - one of the existing tests was enhanced to support the local host verification properly. ---- This patch series implements the first release of the Live (and cold!) migration in Kimchi. *** Requirements for a VM migration *** - shared storage. The disk/storage path must be the same at the origin and destination. - password-less login - enough resorces in the destination host to allocate the VM Limitations of this first version and possible candidates for future work: - non-shared migration not supported - do not automate the process of password-less login (but it is able to verify this condition) - due to the above limitation, the 'user' parameter value is only 'root' at this moment - do not verify the 'shared storage' condition - do not verify same hypervisor/arch conditions - the origin VM will shutdown after migration (not sure if can be helped) *** Curl usage *** curl -u username -H "Content-Type: application/json" -H "Accept: application/json" -k "https://localhost:10001/plugins/kimchi/vms/<VM NAME>/migrate" -X POST -d '{"remote_host":"insert_remote_IP_address", "user":"username"}' *** Unit test usage *** The unit tests for this feature needs an additional parameter in the command line of ./run-tests.sh: $ sudo KIMCHI_LIVE_MIGRATION_TEST=remote_host_IP ./run_tests.sh test_livemigration If this parameter is not supplied the unit test will simply skip. The test also verifies the password-less condition before trying to test a vm migration. Daniel Henrique Barboza (3): Live migration backend: API and messages Live migration backend: control/vms and model/vms changes Live migration backend: unit tests src/wok/plugins/kimchi/API.json | 17 + src/wok/plugins/kimchi/control/vms.py | 3 + src/wok/plugins/kimchi/docs/API.md | 5 + src/wok/plugins/kimchi/i18n.py | 7 + src/wok/plugins/kimchi/model/vms.py | 75 ++++- src/wok/plugins/kimchi/tests/test_livemigration.py | 356 +++++++++++++++++++++ src/wok/plugins/kimchi/ui/js/src/kimchi.api.js | 13 + 7 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 src/wok/plugins/kimchi/tests/test_livemigration.py -- 2.4.3

From: Daniel Henrique Barboza <dhbarboza82@gmail.com> This patch adds messages in i18n to support the live migration backend, which is defined by a new API defined in API.json and docs/API.md. Signed-off-by: Daniel Henrique Barboza <dhbarboza82@gmail.com> --- src/wok/plugins/kimchi/API.json | 17 +++++++++++++++++ src/wok/plugins/kimchi/docs/API.md | 5 +++++ src/wok/plugins/kimchi/i18n.py | 7 +++++++ src/wok/plugins/kimchi/ui/js/src/kimchi.api.js | 13 +++++++++++++ 4 files changed, 42 insertions(+) diff --git a/src/wok/plugins/kimchi/API.json b/src/wok/plugins/kimchi/API.json index c43fa36..de68d70 100644 --- a/src/wok/plugins/kimchi/API.json +++ b/src/wok/plugins/kimchi/API.json @@ -309,6 +309,23 @@ }, "additionalProperties": false }, + "vm_migrate": { + "type": "object", + "properties": { + "remote_host": { + "description": "IP address or hostname of the remote server", + "type": "string", + "minLength": 1, + "error": "KCHVM0060E" + }, + "user": { + "description": "User of the remote server", + "type": "string", + "minLength": 1, + "error": "KCHVM0059E" + } + } + }, "networks_create": { "type": "object", "error": "KCHNET0016E", diff --git a/src/wok/plugins/kimchi/docs/API.md b/src/wok/plugins/kimchi/docs/API.md index eb3905f..52368b7 100644 --- a/src/wok/plugins/kimchi/docs/API.md +++ b/src/wok/plugins/kimchi/docs/API.md @@ -182,6 +182,11 @@ server. * resume: Resume a suspended domain. The process is restarted from the state where it was frozen by calling "suspend". +* migrate: Migrate a virtual machine to a remote server, only support live +mode without block migration. + * remote_host: IP address or hostname of the remote server. + * user: User to log on at the remote server. + ### Sub-resource: Virtual Machine Screenshot **URI:** /plugins/kimchi/vms/*:name*/screenshot diff --git a/src/wok/plugins/kimchi/i18n.py b/src/wok/plugins/kimchi/i18n.py index f9a2ca1..42a5e16 100644 --- a/src/wok/plugins/kimchi/i18n.py +++ b/src/wok/plugins/kimchi/i18n.py @@ -109,6 +109,13 @@ messages = { "KCHVM0050E": _("Cannot shutdown %(name)s. Virtual machine is shut off."), "KCHVM0051E": _("Cannot reset %(name)s. Virtual machine is already shut off."), + "KCHVM0055E": _("Migrate to localhost %(host)s is not allowed."), + "KCHVM0056E": _("To migrate a virtual machine to the remote host %(host)s the user %(user)s must have password-less login to the remote host."), + "KCHVM0057E": _("Can not migrate virtual machine %(name)s when its in %(state)s state."), + "KCHVM0058E": _("Failed to migrate virtual machine %(name)s due error: %(err)s"), + "KCHVM0059E": _("User name of the remote server must be a string."), + "KCHVM0060E": _("Destination host of the migration must be a string."), + "KCHVMHDEV0001E": _("VM %(vmid)s does not contain directly assigned host device %(dev_name)s."), "KCHVMHDEV0002E": _("The host device %(dev_name)s is not allowed to directly assign to VM."), "KCHVMHDEV0003E": _("No IOMMU groups found. Host PCI pass through needs IOMMU group to function correctly. " diff --git a/src/wok/plugins/kimchi/ui/js/src/kimchi.api.js b/src/wok/plugins/kimchi/ui/js/src/kimchi.api.js index c55b252..b1acdb9 100644 --- a/src/wok/plugins/kimchi/ui/js/src/kimchi.api.js +++ b/src/wok/plugins/kimchi/ui/js/src/kimchi.api.js @@ -1018,6 +1018,19 @@ var kimchi = { }); }, + migrateGuest: function(vm, suc, err) { + wok.requestJSON({ + url : 'plugins/kimchi/vms/' + encodeURIComponent(vm) + "/migrate", + type : 'POST', + contentType : 'application/json', + dataType : 'json', + success : suc, + error : err ? err : function(data) { + wok.message.error(data.responseJSON.reason); + } + }); + }, + listSnapshots : function(vm, suc, err) { wok.requestJSON({ url : 'plugins/kimchi/vms/' + encodeURIComponent(vm) + '/snapshots', -- 2.4.3

From: Daniel Henrique Barboza <dhbarboza82@gmail.com> This patch adds the backend code to support live (and cold) migration in Kimchi. Signed-off-by: Daniel Henrique Barboza <dhbarboza82@gmail.com> --- src/wok/plugins/kimchi/control/vms.py | 3 ++ src/wok/plugins/kimchi/model/vms.py | 75 ++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/wok/plugins/kimchi/control/vms.py b/src/wok/plugins/kimchi/control/vms.py index 58d4b67..5e24538 100644 --- a/src/wok/plugins/kimchi/control/vms.py +++ b/src/wok/plugins/kimchi/control/vms.py @@ -49,6 +49,9 @@ class VM(Resource): destructive=True) self.connect = self.generate_action_handler('connect') self.clone = self.generate_action_handler_task('clone') + self.migrate = self.generate_action_handler_task('migrate', + ['remote_host', + 'user']) self.suspend = self.generate_action_handler('suspend') self.resume = self.generate_action_handler('resume') diff --git a/src/wok/plugins/kimchi/model/vms.py b/src/wok/plugins/kimchi/model/vms.py index 6e77bae..63681c7 100644 --- a/src/wok/plugins/kimchi/model/vms.py +++ b/src/wok/plugins/kimchi/model/vms.py @@ -22,6 +22,7 @@ import libvirt import lxml.etree as ET import os import random +import socket import string import threading import time @@ -36,7 +37,7 @@ from wok.exception import NotFoundError, OperationFailed from wok.model.tasks import TaskModel from wok.rollbackcontext import RollbackContext from wok.utils import add_task, convert_data_size, get_next_clone_name -from wok.utils import import_class, run_setfacl_set_attr, wok_log +from wok.utils import import_class, run_setfacl_set_attr, run_command, wok_log from wok.xmlutils.utils import xpath_get_text, xml_item_update from wok.xmlutils.utils import dictize @@ -1309,6 +1310,78 @@ class VMModel(object): raise OperationFailed('KCHVM0040E', {'name': name, 'err': e.message}) + def _check_if_host_not_localhost(self, remote_host): + hostname = socket.gethostname() + + if remote_host in ['localhost', '127.0.0.1', hostname]: + raise OperationFailed("KCHVM0055E", {'host': remote_host}) + + def _check_if_password_less_login_enabled(self, remote_host, user): + username_host = "%s@%s" % (user, remote_host) + ssh_cmd = ['ssh', '-oNumberOfPasswordPrompts=0', + '-oStrictHostKeyChecking=no', username_host, + 'echo', 'hello'] + stdout, stderr, returncode = run_command(ssh_cmd, 5, silent=True) + if returncode != 0: + raise OperationFailed("KCHVM0056E", + {'host': remote_host, 'user': user}) + + def _get_remote_libvirt_conn(self, remote_host, + user='root', transport='ssh'): + dest_uri = 'qemu+%s://%s@%s/system' % (transport, user, remote_host) + # TODO: verify why LibvirtConnection(dest_uri) does not work here + return libvirt.open(dest_uri) + + def migration_pre_check(self, remote_host, user): + self._check_if_host_not_localhost(remote_host) + self._check_if_password_less_login_enabled(remote_host, user) + + def migrate(self, name, remote_host, user='root'): + name = name.decode('utf-8') + remote_host = remote_host.decode('utf-8') + + self.migration_pre_check(remote_host, user) + dest_conn = self._get_remote_libvirt_conn(remote_host) + + params = {'name': name, + 'dest_conn': dest_conn} + task_id = add_task('/vms/%s/migrate' % name, self._migrate_task, + self.objstore, params) + + return self.task.lookup(task_id) + + def _migrate_task(self, cb, params): + name = params['name'].decode('utf-8') + dest_conn = params['dest_conn'] + + cb('starting a migration') + + dom = self.get_vm(name, self.conn) + state = DOM_STATE_MAP[dom.info()[0]] + + flags = libvirt.VIR_MIGRATE_PEER2PEER + if state == 'shutoff': + flags |= (libvirt.VIR_MIGRATE_OFFLINE | + libvirt.VIR_MIGRATE_PERSIST_DEST) + elif state in ['running', 'paused']: + flags |= libvirt.VIR_MIGRATE_LIVE | libvirt.VIR_MIGRATE_TUNNELLED + if dom.isPersistent(): + flags |= libvirt.VIR_MIGRATE_PERSIST_DEST + else: + dest_conn.close() + raise OperationFailed("KCHVM0057E", {'name': name, + 'state': state}) + try: + dom.migrate(dest_conn, flags) + except libvirt.libvirtError as e: + cb('Migrate failed', False) + raise OperationFailed('KCHVM0058E', {'err': e.message, + 'name': name}) + finally: + dest_conn.close() + + cb('Migrate finished', True) + class VMScreenshotModel(object): def __init__(self, **kargs): -- 2.4.3

From: Daniel Henrique Barboza <dhbarboza82@gmail.com> These unit tests requires an additional variable passed in the command line, otherwise it won't be possible to test the migration types. Example: $ sudo KIMCHI_LIVE_MIGRATION_TEST=1.1.1.1 ./run_tests.sh test_livemigration It also depends on the common pre-requisites of migration: - valid remote host - password-less root login at the remote host The unit test will silently skip if the KIMCHI_LIVE_MIGRATION_TEST is not defined to avoid tampering with the common usage of ./run_tests.sh script. Signed-off-by: Daniel Henrique Barboza <dhbarboza82@gmail.com> --- src/wok/plugins/kimchi/tests/test_livemigration.py | 356 +++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 src/wok/plugins/kimchi/tests/test_livemigration.py diff --git a/src/wok/plugins/kimchi/tests/test_livemigration.py b/src/wok/plugins/kimchi/tests/test_livemigration.py new file mode 100644 index 0000000..1fdcd65 --- /dev/null +++ b/src/wok/plugins/kimchi/tests/test_livemigration.py @@ -0,0 +1,356 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2015 +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import json +import libvirt +import os +import socket +import shutil +import unittest +from functools import partial + + +from wok.basemodel import Singleton +from wok.exception import OperationFailed +from wok.rollbackcontext import RollbackContext + + +from wok.plugins.kimchi.model import model +from wok.plugins.kimchi.model.libvirtconnection import LibvirtConnection +from wok.plugins.kimchi.model.vms import VMModel + + +import iso_gen +import utils +from utils import get_free_port, patch_auth, request +from utils import run_server, wait_task + + +TMP_DIR = '/var/lib/kimchi/tests/' +UBUNTU_ISO = TMP_DIR + 'ubuntu14.04.iso' +KIMCHI_LIVE_MIGRATION_TEST = None + + +def setUpModule(): + if not os.path.exists(TMP_DIR): + os.makedirs(TMP_DIR) + iso_gen.construct_fake_iso(UBUNTU_ISO, True, '14.04', 'ubuntu') + # Some FeatureTests functions depend on server to validate their result. + # As CapabilitiesModel is a Singleton class it will get the first result + # from FeatureTests which may be wrong when using the Model instance + # directly - the case of this test_model.py + # So clean Singleton instances to make sure to get the right result when + # running the following tests. + Singleton._instances = {} + + +def tearDownModule(): + shutil.rmtree(TMP_DIR) + + +def remoteserver_environment_defined(): + global KIMCHI_LIVE_MIGRATION_TEST + KIMCHI_LIVE_MIGRATION_TEST = os.environ.get('KIMCHI_LIVE_MIGRATION_TEST') + return KIMCHI_LIVE_MIGRATION_TEST is not None + + +def running_root_and_remoteserver_defined(): + return utils.running_as_root() and remoteserver_environment_defined() + + +def check_if_vm_migration_test_possible(): + inst = model.Model(objstore_loc='/tmp/kimchi-store-test') + try: + inst.vm_migration_pre_check(KIMCHI_LIVE_MIGRATION_TEST, 'root') + except: + return False + return True + + +@unittest.skipUnless(running_root_and_remoteserver_defined(), + 'Must be run as root and with a remote server ' + 'defined in the KIMCHI_LIVE_MIGRATION_TEST variable') +class LiveMigrationTests(unittest.TestCase): + def setUp(self): + self.tmp_store = '/tmp/kimchi-store-test' + self.inst = model.Model(objstore_loc=self.tmp_store) + params = {'name': u'template_test_vm_migrate', + 'disks': [], + 'cdrom': UBUNTU_ISO, + 'memory': 2048, + 'max_memory': 4096*1024} + self.inst.templates_create(params) + + def tearDown(self): + self.inst.template_delete('template_test_vm_migrate') + + os.unlink(self.tmp_store) + + def create_vm_test(self): + params = { + 'name': u'test_vm_migrate', + 'template': u'/plugins/kimchi/templates/template_test_vm_migrate' + } + task = self.inst.vms_create(params) + self.inst.task_wait(task['id']) + + def test_vm_migrate_fails_if_remote_is_localhost(self): + with RollbackContext() as rollback: + self.create_vm_test() + rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete, + u'test_vm_migrate') + + self.assertRaises(OperationFailed, + self.inst.vm_migrate, + 'test_vm_migrate', + '127.0.0.1') + + self.assertRaises(OperationFailed, + self.inst.vm_migrate, + 'test_vm_migrate', + 'localhost') + + hostname = socket.gethostname() + + self.assertRaises(OperationFailed, + self.inst.vm_migrate, + 'test_vm_migrate', + hostname) + + def test_vm_migrate_fails_if_remotehost_unreachable(self): + with RollbackContext() as rollback: + self.create_vm_test() + rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete, + u'test_vm_migrate') + + self.assertRaises(OperationFailed, + self.inst.vm_migrate, + 'test_vm_migrate', + 'test.vm.migrate.host.unreachable') + + def test_vm_migrate_fails_if_not_passwordless_login(self): + with RollbackContext() as rollback: + self.create_vm_test() + rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete, + u'test_vm_migrate') + + self.assertRaises(OperationFailed, + self.inst.vm_migrate, + 'test_vm_migrate', + KIMCHI_LIVE_MIGRATION_TEST, + user='test_vm_migrate_fake_user') + + def get_remote_conn(self): + remote_uri = 'qemu+ssh://%s@%s/system' % \ + ('root', KIMCHI_LIVE_MIGRATION_TEST) + remote_conn = libvirt.open(remote_uri) + return remote_conn + + def get_remote_vm_list(self): + remote_uri = 'qemu+ssh://%s@%s/system' % \ + ('root', KIMCHI_LIVE_MIGRATION_TEST) + remote_conn = libvirt.open(remote_uri) + return [vm.name() for vm in remote_conn.listAllDomains()] + + @unittest.skipUnless(check_if_vm_migration_test_possible(), + 'not possible to test a live migration') + def test_vm_livemigrate_persistent(self): + inst = model.Model(libvirt_uri='qemu:///system', + objstore_loc=self.tmp_store) + + with RollbackContext() as rollback: + self.create_vm_test() + rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete, + u'test_vm_migrate') + + # removing cdrom because it is not shared storage and will make + # the migration fail + dev_list = self.inst.vmstorages_get_list('test_vm_migrate') + self.inst.vmstorage_delete('test_vm_migrate', dev_list[0]) + + try: + self.inst.vm_start('test_vm_migrate') + except Exception, e: + self.fail('Failed to start the vm, reason: %s' % e.message) + try: + task = inst.vm_migrate('test_vm_migrate', + KIMCHI_LIVE_MIGRATION_TEST) + inst.task_wait(task['id']) + self.assertIn('test_vm_migrate', self.get_remote_vm_list()) + + remote_conn = self.get_remote_conn() + rollback.prependDefer(remote_conn.close) + + remote_vm = remote_conn.lookupByName('test_vm_migrate') + self.assertTrue(remote_vm.isPersistent()) + + remote_vm.destroy() + remote_vm.undefine() + except Exception, e: + self.fail('Migration test failed: %s' % e.message) + + @unittest.skipUnless(check_if_vm_migration_test_possible(), + 'not possible to test a live migration') + def test_vm_livemigrate_transient(self): + inst = model.Model(libvirt_uri='qemu:///system', + objstore_loc=self.tmp_store) + + self.create_vm_test() + + with RollbackContext() as rollback: + try: + # removing cdrom because it is not shared storage and will make + # the migration fail + dev_list = self.inst.vmstorages_get_list('test_vm_migrate') + self.inst.vmstorage_delete('test_vm_migrate', dev_list[0]) + + self.inst.vm_start('test_vm_migrate') + + # to make the VM transient, undefine it while it's running + vm = VMModel.get_vm( + 'test_vm_migrate', + LibvirtConnection('qemu:///system') + ) + vm.undefine() + + task = inst.vm_migrate('test_vm_migrate', + KIMCHI_LIVE_MIGRATION_TEST) + inst.task_wait(task['id']) + self.assertIn('test_vm_migrate', self.get_remote_vm_list()) + + remote_conn = self.get_remote_conn() + rollback.prependDefer(remote_conn.close) + + remote_vm = remote_conn.lookupByName('test_vm_migrate') + self.assertFalse(remote_vm.isPersistent()) + + remote_vm.destroy() + except Exception, e: + # Clean up here instead of rollback because if the + # VM was turned transient and shut down it might + # not exist already - rollback in this case will cause + # a QEMU error + vm = VMModel.get_vm( + 'test_vm_migrate', + LibvirtConnection('qemu:///system') + ) + if vm.isPersistent(): + vm.undefine() + vm.shutdown() + self.fail('Migration test failed: %s' % e.message) + + @unittest.skipUnless(check_if_vm_migration_test_possible(), + 'not possible to test shutdown migration') + def test_vm_coldmigrate(self): + inst = model.Model(libvirt_uri='qemu:///system', + objstore_loc=self.tmp_store) + + with RollbackContext() as rollback: + self.create_vm_test() + rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete, + u'test_vm_migrate') + + # removing cdrom because it is not shared storage and will make + # the migration fail + dev_list = self.inst.vmstorages_get_list('test_vm_migrate') + self.inst.vmstorage_delete('test_vm_migrate', dev_list[0]) + + try: + task = inst.vm_migrate('test_vm_migrate', + KIMCHI_LIVE_MIGRATION_TEST) + inst.task_wait(task['id']) + self.assertIn('test_vm_migrate', self.get_remote_vm_list()) + + remote_conn = self.get_remote_conn() + rollback.prependDefer(remote_conn.close) + + remote_vm = remote_conn.lookupByName('test_vm_migrate') + self.assertTrue(remote_vm.isPersistent()) + + state = remote_vm.info()[0] + self.assertEqual(state, libvirt.VIR_DOMAIN_SHUTOFF) + + remote_vm.undefine() + except Exception, e: + self.fail('Migration test failed: %s' % e.message) + + def _task_lookup(self, taskid): + return json.loads( + self.request('/plugins/kimchi/tasks/%s' % taskid).read() + ) + + @unittest.skipUnless(check_if_vm_migration_test_possible(), + 'not possible to test a live migration') + def test_vm_livemigrate_persistent_API(self): + patch_auth() + + inst = model.Model(libvirt_uri='qemu:///system', + objstore_loc=self.tmp_store) + + host = '127.0.0.1' + port = get_free_port('http') + ssl_port = get_free_port('https') + cherrypy_port = get_free_port('cherrypy_port') + + with RollbackContext() as rollback: + test_server = run_server(host, port, ssl_port, test_mode=True, + cherrypy_port=cherrypy_port, model=inst) + rollback.prependDefer(test_server.stop) + + self.request = partial(request, host, ssl_port) + + self.create_vm_test() + rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete, + u'test_vm_migrate') + + # removing cdrom because it is not shared storage and will make + # the migration fail + dev_list = self.inst.vmstorages_get_list('test_vm_migrate') + self.inst.vmstorage_delete('test_vm_migrate', dev_list[0]) + + try: + self.inst.vm_start('test_vm_migrate') + except Exception, e: + self.fail('Failed to start the vm, reason: %s' % e.message) + + migrate_url = "/plugins/kimchi/vms/%s/migrate" % 'test_vm_migrate' + + req = json.dumps({'remote_host': KIMCHI_LIVE_MIGRATION_TEST, + 'user': 'root'}) + resp = self.request(migrate_url, req, 'POST') + self.assertEquals(202, resp.status) + task = json.loads(resp.read()) + wait_task(self._task_lookup, task['id']) + task = json.loads( + self.request( + '/plugins/kimchi/tasks/%s' % task['id'], + '{}' + ).read() + ) + self.assertEquals('finished', task['status']) + + try: + remote_conn = self.get_remote_conn() + rollback.prependDefer(remote_conn.close) + remote_vm = remote_conn.lookupByName('test_vm_migrate') + self.assertTrue(remote_vm.isPersistent()) + remote_vm.destroy() + remote_vm.undefine() + except Exception, e: + self.fail('Migration test failed: %s' % e.message) -- 2.4.3

Reviewed-By: Paulo Vital <pvital@linux.vnet.ibm.com> On Thu, 2015-10-29 at 17:48 -0200, dhbarboza82@gmail.com wrote:
From: Daniel Henrique Barboza <dhbarboza82@gmail.com>
Changes in v3:
- get hostname by using python socket API instead of run_command with 'hostname' command - closed remote connection in _migrate_task - removed 'test:///default' declaration in test files - added a test to cover the API
Changes in v2:
- changes asked by pvital: verifying host name properly, break line in docs/API.md, changed the format of i18n messages. - one of the existing tests was enhanced to support the local host verification properly.
----
This patch series implements the first release of the Live (and cold!) migration in Kimchi.
*** Requirements for a VM migration ***
- shared storage. The disk/storage path must be the same at the origin and destination.
- password-less login
- enough resorces in the destination host to allocate the VM
Limitations of this first version and possible candidates for future work:
- non-shared migration not supported
- do not automate the process of password-less login (but it is able to verify this condition)
- due to the above limitation, the 'user' parameter value is only 'root' at this moment
- do not verify the 'shared storage' condition
- do not verify same hypervisor/arch conditions
- the origin VM will shutdown after migration (not sure if can be helped)
*** Curl usage ***
curl -u username -H "Content-Type: application/json" -H "Accept: application/json" -k "https://localhost:10001/plugins/kimchi/vms/<VM NAME>/migrate" -X POST -d '{"remote_host":"insert_remote_IP_address", "user":"username"}'
*** Unit test usage ***
The unit tests for this feature needs an additional parameter in the command line of ./run-tests.sh:
$ sudo KIMCHI_LIVE_MIGRATION_TEST=remote_host_IP ./run_tests.sh test_livemigration
If this parameter is not supplied the unit test will simply skip. The test also verifies the password-less condition before trying to test a vm migration.
Daniel Henrique Barboza (3): Live migration backend: API and messages Live migration backend: control/vms and model/vms changes Live migration backend: unit tests
src/wok/plugins/kimchi/API.json | 17 + src/wok/plugins/kimchi/control/vms.py | 3 + src/wok/plugins/kimchi/docs/API.md | 5 + src/wok/plugins/kimchi/i18n.py | 7 + src/wok/plugins/kimchi/model/vms.py | 75 ++++- src/wok/plugins/kimchi/tests/test_livemigration.py | 356 +++++++++++++++++++++ src/wok/plugins/kimchi/ui/js/src/kimchi.api.js | 13 + 7 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 src/wok/plugins/kimchi/tests/test_livemigration.py
participants (3)
-
Aline Manera
-
dhbarboza82@gmail.com
-
Paulo Ricardo Paz Vital