[PATCH 0/3] Live migration backend

From: Daniel Henrique Barboza <dhbarboza82@gmail.com> 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: - 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 | 4 + src/wok/plugins/kimchi/i18n.py | 7 + src/wok/plugins/kimchi/model/vms.py | 68 +++++- src/wok/plugins/kimchi/tests/test_livemigration.py | 268 +++++++++++++++++++++ src/wok/plugins/kimchi/ui/js/src/kimchi.api.js | 13 + 7 files changed, 379 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 | 4 ++++ src/wok/plugins/kimchi/i18n.py | 7 +++++++ src/wok/plugins/kimchi/ui/js/src/kimchi.api.js | 13 +++++++++++++ 4 files changed, 41 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 a9333b5..e9fb35e 100644 --- a/src/wok/plugins/kimchi/docs/API.md +++ b/src/wok/plugins/kimchi/docs/API.md @@ -180,6 +180,10 @@ 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..e67f6f0 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 vm to the remote host %(host)s the user %(user)s must have password-less login to the remote host."), + "KCHVM0057E": _("Can not migrate vm %(name)s when its in %(state)s state."), + "KCHVM0058E": _("Failed Migrate vm %(name)s due error: %(err)s"), + "KCHVM0059E": _("User name of the remote server must be a string"), + "KCHVM0060E": _("Migrate remote_host 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 6ffa0df..58693f5 100644 --- a/src/wok/plugins/kimchi/ui/js/src/kimchi.api.js +++ b/src/wok/plugins/kimchi/ui/js/src/kimchi.api.js @@ -1007,6 +1007,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

On Mon, 2015-10-26 at 16:08 -0200, dhbarboza82@gmail.com wrote:
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 | 4 ++++ src/wok/plugins/kimchi/i18n.py | 7 +++++++ src/wok/plugins/kimchi/ui/js/src/kimchi.api.js | 13 +++++++++++++ 4 files changed, 41 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 a9333b5..e9fb35e 100644 --- a/src/wok/plugins/kimchi/docs/API.md +++ b/src/wok/plugins/kimchi/docs/API.md @@ -180,6 +180,10 @@ 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.
Would be nice break the line at the 80 collumm :-P
+ * 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..e67f6f0 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 vm to the remote host %(host)s the user %(user)s must have password-less login to the remote host."),
All messages are not using the term "vm" (or "VM"), but "Virtual Machine". Would be nice keep the pattern.
+ "KCHVM0057E": _("Can not migrate vm %(name)s when its in %(state)s state."), + "KCHVM0058E": _("Failed Migrate vm %(name)s due error: %(err)s"),
I guess it should be "Failed to migrate".
+ "KCHVM0059E": _("User name of the remote server must be a string"), + "KCHVM0060E": _("Migrate remote_host must be a string"),
Is this message correct?
+ "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 6ffa0df..58693f5 100644 --- a/src/wok/plugins/kimchi/ui/js/src/kimchi.api.js +++ b/src/wok/plugins/kimchi/ui/js/src/kimchi.api.js @@ -1007,6 +1007,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',

On 10/27/2015 08:38 AM, Paulo Ricardo Paz Vital wrote:
On Mon, 2015-10-26 at 16:08 -0200, dhbarboza82@gmail.com wrote:
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 | 4 ++++ src/wok/plugins/kimchi/i18n.py | 7 +++++++ src/wok/plugins/kimchi/ui/js/src/kimchi.api.js | 13 +++++++++++++ 4 files changed, 41 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 a9333b5..e9fb35e 100644 --- a/src/wok/plugins/kimchi/docs/API.md +++ b/src/wok/plugins/kimchi/docs/API.md @@ -180,6 +180,10 @@ 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. Would be nice break the line at the 80 collumm :-P
+ * 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..e67f6f0 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 vm to the remote host %(host)s the user %(user)s must have password-less login to the remote host."), All messages are not using the term "vm" (or "VM"), but "Virtual Machine". Would be nice keep the pattern. Ok!
+ "KCHVM0057E": _("Can not migrate vm %(name)s when its in %(state)s state."), + "KCHVM0058E": _("Failed Migrate vm %(name)s due error: %(err)s"), I guess it should be "Failed to migrate".
Ok!
+ "KCHVM0059E": _("User name of the remote server must be a string"), + "KCHVM0060E": _("Migrate remote_host must be a string"), Is this message correct?
It's a bit strange, but it is. I'll improve it It means that the destination host in the API must be a string (or IP address)
+ "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 6ffa0df..58693f5 100644 --- a/src/wok/plugins/kimchi/ui/js/src/kimchi.api.js +++ b/src/wok/plugins/kimchi/ui/js/src/kimchi.api.js @@ -1007,6 +1007,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',

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 | 68 ++++++++++++++++++++++++++++++++++- 2 files changed, 70 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 60145d0..5d242e8 100644 --- a/src/wok/plugins/kimchi/model/vms.py +++ b/src/wok/plugins/kimchi/model/vms.py @@ -36,7 +36,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 @@ -1294,6 +1294,72 @@ class VMModel(object): raise OperationFailed('KCHVM0040E', {'name': name, 'err': e.message}) + def _check_if_host_not_localhost(self, remote_host): + if remote_host in ['localhost', '127.0.0.1']: + 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: + 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}) + cb('Migrate finished', True) + class VMScreenshotModel(object): def __init__(self, **kargs): -- 2.4.3

On Mon, 2015-10-26 at 16:08 -0200, dhbarboza82@gmail.com wrote:
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 | 68 ++++++++++++++++++++++++++++++++++- 2 files changed, 70 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 60145d0..5d242e8 100644 --- a/src/wok/plugins/kimchi/model/vms.py +++ b/src/wok/plugins/kimchi/model/vms.py @@ -36,7 +36,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
@@ -1294,6 +1294,72 @@ class VMModel(object): raise OperationFailed('KCHVM0040E', {'name': name, 'err': e.message})
+ def _check_if_host_not_localhost(self, remote_host): + if remote_host in ['localhost', '127.0.0.1']: + raise OperationFailed("KCHVM0055E", {'host': remote_host})
Only a curiosity: Is it OK accept the hostname or IP address of my 'localhost', or only these two cases are not a good case to migrate?
+ + 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: + 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}) + cb('Migrate finished', True) +
class VMScreenshotModel(object): def __init__(self, **kargs):

On 10/27/2015 08:38 AM, Paulo Ricardo Paz Vital wrote:
On Mon, 2015-10-26 at 16:08 -0200, dhbarboza82@gmail.com wrote:
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 | 68 ++++++++++++++++++++++++++++++++++- 2 files changed, 70 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 60145d0..5d242e8 100644 --- a/src/wok/plugins/kimchi/model/vms.py +++ b/src/wok/plugins/kimchi/model/vms.py @@ -36,7 +36,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
@@ -1294,6 +1294,72 @@ class VMModel(object): raise OperationFailed('KCHVM0040E', {'name': name, 'err': e.message})
+ def _check_if_host_not_localhost(self, remote_host): + if remote_host in ['localhost', '127.0.0.1']: + raise OperationFailed("KCHVM0055E", {'host': remote_host}) Only a curiosity: Is it OK accept the hostname or IP address of my 'localhost', or only these two cases are not a good case to migrate? Libvirt will freak out if you try to do a 'migrateinception' :P
This is why I've made this verification - to ensure that the user doesn't pass the IP/hostname of the same host Kimchi is running on by accident.
+ + 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: + 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}) + cb('Migrate finished', True) +
class VMScreenshotModel(object): def __init__(self, **kargs):

On Tue, 2015-10-27 at 11:37 -0200, Daniel Henrique Barboza wrote:
On 10/27/2015 08:38 AM, Paulo Ricardo Paz Vital wrote:
On Mon, 2015-10-26 at 16:08 -0200, dhbarboza82@gmail.com wrote:
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 | 68 ++++++++++++++++++++++++++++++++++- 2 files changed, 70 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 60145d0..5d242e8 100644 --- a/src/wok/plugins/kimchi/model/vms.py +++ b/src/wok/plugins/kimchi/model/vms.py @@ -36,7 +36,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
@@ -1294,6 +1294,72 @@ class VMModel(object): raise OperationFailed('KCHVM0040E', {'name': name, 'err': e.message})
+ def _check_if_host_not_localhost(self, remote_host): + if remote_host in ['localhost', '127.0.0.1']: + raise OperationFailed("KCHVM0055E", {'host': remote_host}) Only a curiosity: Is it OK accept the hostname or IP address of my 'localhost', or only these two cases are not a good case to migrate? Libvirt will freak out if you try to do a 'migrateinception' :P
This is why I've made this verification - to ensure that the user doesn't pass the IP/hostname of the same host Kimchi is running on by accident.
So, if I'm running Kimchi in a machine called 'bla.br.ibm.com' and use this hostname to realize a migration, libvirt will freakout, right?!?! I suggest to check not only the name 'localhost' and IP '127.0.0.1', but also the IP address and real hostname of the machine.
+ + 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: + 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}) + cb('Migrate finished', True) +
class VMScreenshotModel(object): def __init__(self, **kargs):

On 10/27/2015 03:11 PM, Paulo Ricardo Paz Vital wrote:
On Tue, 2015-10-27 at 11:37 -0200, Daniel Henrique Barboza wrote:
On 10/27/2015 08:38 AM, Paulo Ricardo Paz Vital wrote:
On Mon, 2015-10-26 at 16:08 -0200, dhbarboza82@gmail.com wrote:
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 | 68 ++++++++++++++++++++++++++++++++++- 2 files changed, 70 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 60145d0..5d242e8 100644 --- a/src/wok/plugins/kimchi/model/vms.py +++ b/src/wok/plugins/kimchi/model/vms.py @@ -36,7 +36,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
@@ -1294,6 +1294,72 @@ class VMModel(object): raise OperationFailed('KCHVM0040E', {'name': name, 'err': e.message})
+ def _check_if_host_not_localhost(self, remote_host): + if remote_host in ['localhost', '127.0.0.1']: + raise OperationFailed("KCHVM0055E", {'host': remote_host}) Only a curiosity: Is it OK accept the hostname or IP address of my 'localhost', or only these two cases are not a good case to migrate? Libvirt will freak out if you try to do a 'migrateinception' :P
This is why I've made this verification - to ensure that the user doesn't pass the IP/hostname of the same host Kimchi is running on by accident.
So, if I'm running Kimchi in a machine called 'bla.br.ibm.com' and use this hostname to realize a migration, libvirt will freakout, right?!?!
I suggest to check not only the name 'localhost' and IP '127.0.0.1', but also the IP address and real hostname of the machine.
hahahaha you're right I'll fix it in v2
+ + 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: + 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}) + cb('Migrate finished', True) +
class VMScreenshotModel(object): def __init__(self, **kargs):

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 | 268 +++++++++++++++++++++ 1 file changed, 268 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..c88bcec --- /dev/null +++ b/src/wok/plugins/kimchi/tests/test_livemigration.py @@ -0,0 +1,268 @@ +# +# 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 libvirt +import os +import shutil +import unittest + +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 + + +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') + + # FIXME: Tests using 'test:///default' URI should be moved to + # test_rest or test_mockmodel to avoid overriding problems + LibvirtConnection._connections['test:///default'] = {} + + 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') + + 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() + 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() + + 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() + 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() + 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) -- 2.4.3
participants (3)
-
Daniel Henrique Barboza
-
dhbarboza82@gmail.com
-
Paulo Ricardo Paz Vital