[v1 0/1]Migrate virtual machines

This patch maybe bugy, only send out to get your early comments Simon Jin (1): Migrate virtual machines docs/API.md | 3 +- src/kimchi/control/vms.py | 1 + src/kimchi/exception.py | 3 + src/kimchi/i18n.py | 11 ++++ src/kimchi/model/vms.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 1 deletion(-) -- 1.8.3.1

Signed-off-by: Simon Jin <simonjin@linux.vnet.ibm.com> --- docs/API.md | 3 +- src/kimchi/control/vms.py | 1 + src/kimchi/exception.py | 3 + src/kimchi/i18n.py | 11 ++++ src/kimchi/model/vms.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 1 deletion(-) diff --git a/docs/API.md b/docs/API.md index 92fbbd5..28dbc6e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -132,7 +132,8 @@ the following general conventions: It emulates the power reset button on a machine. Note that there is a risk of data loss caused by reset without the guest OS shutdown. * connect: Prepare the connection for spice or vnc - +* migrate: Migrate a VM to remote server, only support live mode with out block migration. + ### Sub-resource: Virtual Machine Screenshot **URI:** /vms/*:name*/screenshot diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index 88d8a81..64b88ad 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -42,6 +42,7 @@ class VM(Resource): for ident, node in sub_nodes.items(): setattr(self, ident, node(model, self.ident)) self.start = self.generate_action_handler('start') + self.migrate = self.generate_action_handler('migrate', 'remoteHost', 'user', 'passwd') self.poweroff = self.generate_action_handler('poweroff') self.shutdown = self.generate_action_handler('shutdown') self.reset = self.generate_action_handler('reset') diff --git a/src/kimchi/exception.py b/src/kimchi/exception.py index 039152a..732b3ee 100644 --- a/src/kimchi/exception.py +++ b/src/kimchi/exception.py @@ -100,3 +100,6 @@ class TimeoutExpired(KimchiException): class UnauthorizedError(KimchiException): pass + +class MigrationError(KimchiException): + pass \ No newline at end of file diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 75fb076..2fd1073 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -101,6 +101,17 @@ messages = { "KCHVM0030E": _("Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"), "KCHVM0031E": _("The guest console password must be a string."), "KCHVM0032E": _("The life time for the guest console password must be a number."), + "KCHVM0033E": _("Can't migrate to localhost."), + "KCHVM0034E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."), + "KCHVM0035E": _("Can't migrate from %(srchyp)s to a different Hypervisor type %(desthyp)s."), + "KCHVM0036E": _("Can't migrate vm %(name)s,which is already in migrating state."), + "KCHVM0037E": _("Can't migrate vm %(name)s,which isn't in running state."), + "KCHVM0038E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)s."), + "KCHVM0039E": _("Can't migrate vm %(name)s, vm already exist on destination server %(remoteHost)s."), + "KCHVM0040E": _("Failed Migrate vm %(name)s due error: %(err)s"), + "KCHVM0041E": _("Failed resume vm %(name)s from migration recover. Details: %(err)s"), + "KCHVM0042E": _("Failed destory vm %(name)s from migration recover. Details: %(err)s"), + "KCHVM0043E": _("Failed disconnect %(remoteuri)s when finish migrate vm %(name)s. Details: %(err)s"), "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."), diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 1089464..1c4eef0 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -44,6 +44,11 @@ from kimchi.utils import import_class, kimchi_log, run_setfacl_set_attr from kimchi.utils import template_name_from_uri from kimchi.xmlutils.utils import xpath_get_text, xml_item_update +from kimchi.model.libvirtconnection import LibvirtConnection +from kimchi.utils import add_task +from kimchi.model.templates import TemplateModel +from kimchi.model.vmhostdevs import VMHostDevsModel +from kimchi.exception import MigrationError DOM_STATE_MAP = {0: 'nostate', 1: 'running', @@ -507,6 +512,139 @@ class VMModel(object): except libvirt.libvirtError as e: raise OperationFailed("KCHVM0019E", {'name': name, 'err': e.get_error_message()}) + + def _preCheck(self, name, destConn, remoteHost): + kimchi_log.debug('precheck migrate %s' % name) + + if remoteHost == 'localhost' or '127.0.0.1': + kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name) + raise MigrationError("KCHVM00033E", {'name': name, + 'remoteHost': remoteHost}) + + + if self.conn.get().getInfo()[0] != destConn.getInfo()[0]: + kimchi_log.debug('vm %s can not migrate to different arch server.' % (name, destConn.getInfo()[0])) + raise MigrationError("KCHVM00034E", {'name': name, + 'srcarch': self.conn.get().getType(), + 'destarch': self.destConn.getType()}) + + if self.conn.get().getType() != self.destConn.getType(): + kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name) + raise MigrationError("KCHVM00035E", {'name': self.name, + 'srchyp':self.conn.get().getType(), + 'desthyp': self.destConn.getType()}) + + dom = self.vm.get_vm(self.name, self.conn) + + state = DOM_STATE_MAP[dom.info()[0]] + if state == 'migrating': #TODO how to set vm as migrating status ? + kimchi_log.debug('vm is already in migrating %s' % name) + raise MigrationError("KCHVM00036E", {'name': self.name,}) + elif state != 'running': + kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name) + raise MigrationError("KCHVM00037E", {'name': self.name}) + + #check if there is any passthrough devices belong to this vm + vmhostdevs = VMHostDevsModel(conn = self.conn) + hostdevs = vmhostdevs.get_list(self.name) + if hostdevs: + raise MigrationError("KCHVM00038E", {'name' : name, + 'hostdevs': hostdevs}) + + if self.get_vm(name, destConn): + kimchi_log.debug('migrate vm %s already exist on %s' % (name, destConn.getURI())) + raise MigrationError("KCHVM00039E", {'name' : name, + 'remoteHost': remoteHost}) + + + def _getRemoteConn(self, remoteHost, user): + #open destination connect to migrate + transport = 'ssh' + duri = 'qemu+%s://%s@%s/system' % (transport, + user,remoteHost) + + conn = LibvirtConnection(duri) + destConn = conn.get() + return destConn + + def _preMigrate(self, name): + #can't update VM while in Migration source/destination + kimchi_log.debug('prepare migrate %s' % name) + dom = self.get_vm(name, self.conn) + dom.migrateSetMaxDowntime(0) + + + def _do_migrate(self, name, destConn, remoteHost, cb): + + cb('starting a migration') + kimchi_log.debug('migrate %s start' % name) + + flag = 0 + flag |= (libvirt.VIR_MIGRATE_LIVE | + libvirt.VIR_MIGRATE_PEER2PEER | + libvirt.VIR_MIGRATE_TUNNELLED) + #libvirt.VIR_MIGRATE_ABORT_ON_ERROR) + + try: + self.vm.migrate(destConn, None, None,flag, + None, 0) + except libvirt.libvirtError as e: + kimchi_log.error('migrate %s to %s failed' % (name, + remoteHost)) + self._recover(cb) + raise MigrationError('KCHPOOL0040E', {'err': e.message, + 'name': name}) + finally: + self._finish(name, destConn, cb) + + def migrate(self, name, remoteHost, user, passwd ): + destconn = self._getRemoteConn(remoteHost, user) + + self._preCheck(name, destconn, remoteHost) + self._preMigrate(name) + + task_id = add_task('/vms/%s/migrate' % name, self._do_migrate, self.objstore, + {'name' : name, 'destconn': destconn, 'remoteHost': remoteHost}) + + # Record vm migrate task for future querying + try: + with self.objstore as session: + session.store('migrate', self.name, task_id) + return task_id + except Exception as e: + raise MigrationError('KCHPOOL0037E', {'err': e.message}) + + def _recover(self, name, destconn, cb): + #recover + cb('recover from a failed migration',False) + kimchi_log.debug('recover from a failed migrate %s' % name) + dom = self.get_vm(name, self.conn) + state = DOM_STATE_MAP[dom.info()[0]] + if state == 'paused': + try: + dom.resume() + except libvirt.libvirtError as e: + raise OperationFailed("KCHVM0041E", + {'name': name, 'err': e.get_error_message()}) + try: + destvm = self.get_vm(name, destconn) + destvm.destroy() + kimchi_log.error("destory migrate vm on destination server") + #destory and undefine + except libvirt.libvirtError as e: + raise MigrationError('KCHPOOL0042E', {'name' : name, + 'err': e.message}) + + def _finish(self, name, destConnc, cb): + kimchi_log.debug('finished migrate %s' % name) + try: + destConnc.close() + except libvirt.libvirtError as e: + raise MigrationError('KCHPOOL0043E', {'name' : name, + 'remoteuri' : destConnc.getURI(), + 'err': e.message}) + finally: + cb('Migrate finished', True) def poweroff(self, name): dom = self.get_vm(name, self.conn) -- 1.8.3.1

On 11/03/2014 12:12 PM, Simon Jin wrote:
Signed-off-by: Simon Jin <simonjin@linux.vnet.ibm.com> --- docs/API.md | 3 +- src/kimchi/control/vms.py | 1 + src/kimchi/exception.py | 3 + src/kimchi/i18n.py | 11 ++++ src/kimchi/model/vms.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 1 deletion(-)
diff --git a/docs/API.md b/docs/API.md index 92fbbd5..28dbc6e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -132,7 +132,8 @@ the following general conventions: It emulates the power reset button on a machine. Note that there is a risk of data loss caused by reset without the guest OS shutdown. * connect: Prepare the connection for spice or vnc - +* migrate: Migrate a VM to remote server, only support live mode with out block migration. +
As the "migrate" operation will require some data like host, username and password, you need to describe them too. You can see the POST request to have an idea on how to do it.
### Sub-resource: Virtual Machine Screenshot
**URI:** /vms/*:name*/screenshot diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index 88d8a81..64b88ad 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -42,6 +42,7 @@ class VM(Resource): for ident, node in sub_nodes.items(): setattr(self, ident, node(model, self.ident)) self.start = self.generate_action_handler('start')
+ self.migrate = self.generate_action_handler('migrate', 'remoteHost', 'user', 'passwd')
Migration will take some time to complete so it is better to use self.generate_action_handler_task() which will expect a Task instead of a new resource. Also self.generate_action_handler*() accepts only one parameter which is the action name. Example: self.generate_action_handler_task('migrate') All the other parameters will come on a dict when hits the model code. So on model/vms.py you will have; def migrate(self, name, params): And "params" will have all the information you need: host, username and password.
self.poweroff = self.generate_action_handler('poweroff') self.shutdown = self.generate_action_handler('shutdown') self.reset = self.generate_action_handler('reset') diff --git a/src/kimchi/exception.py b/src/kimchi/exception.py index 039152a..732b3ee 100644 --- a/src/kimchi/exception.py +++ b/src/kimchi/exception.py @@ -100,3 +100,6 @@ class TimeoutExpired(KimchiException):
class UnauthorizedError(KimchiException): pass
+ +class MigrationError(KimchiException): + pass
You don't need to create a new type of error. You can use OperationFailed
\ No newline at end of file diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 75fb076..2fd1073 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -101,6 +101,17 @@ messages = { "KCHVM0030E": _("Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"), "KCHVM0031E": _("The guest console password must be a string."), "KCHVM0032E": _("The life time for the guest console password must be a number."), + "KCHVM0033E": _("Can't migrate to localhost."),
+ "KCHVM0034E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."), + "KCHVM0035E": _("Can't migrate from %(srchyp)s to a different Hypervisor type %(desthyp)s."),
What is the difference between those 2 messages?
+ "KCHVM0036E": _("Can't migrate vm %(name)s,which is already in migrating state."), + "KCHVM0037E": _("Can't migrate vm %(name)s,which isn't in running state."),
Does it mean I can not migrate a powered off vm?
+ "KCHVM0038E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)s."), + "KCHVM0039E": _("Can't migrate vm %(name)s, vm already exist on destination server %(remoteHost)s."), + "KCHVM0040E": _("Failed Migrate vm %(name)s due error: %(err)s"), + "KCHVM0041E": _("Failed resume vm %(name)s from migration recover. Details: %(err)s"), + "KCHVM0042E": _("Failed destory vm %(name)s from migration recover. Details: %(err)s"), + "KCHVM0043E": _("Failed disconnect %(remoteuri)s when finish migrate vm %(name)s. Details: %(err)s"),
"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."), diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 1089464..1c4eef0 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -44,6 +44,11 @@ from kimchi.utils import import_class, kimchi_log, run_setfacl_set_attr from kimchi.utils import template_name_from_uri from kimchi.xmlutils.utils import xpath_get_text, xml_item_update
+from kimchi.model.libvirtconnection import LibvirtConnection +from kimchi.utils import add_task +from kimchi.model.templates import TemplateModel +from kimchi.model.vmhostdevs import VMHostDevsModel +from kimchi.exception import MigrationError
The imports must be in alphabetic order.
DOM_STATE_MAP = {0: 'nostate', 1: 'running', @@ -507,6 +512,139 @@ class VMModel(object): except libvirt.libvirtError as e: raise OperationFailed("KCHVM0019E", {'name': name, 'err': e.get_error_message()}) + + def _preCheck(self, name, destConn, remoteHost): + kimchi_log.debug('precheck migrate %s' % name) + + if remoteHost == 'localhost' or '127.0.0.1': + kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name) + raise MigrationError("KCHVM00033E", {'name': name, + 'remoteHost': remoteHost}) + + + if self.conn.get().getInfo()[0] != destConn.getInfo()[0]: + kimchi_log.debug('vm %s can not migrate to different arch server.' % (name, destConn.getInfo()[0])) + raise MigrationError("KCHVM00034E", {'name': name, + 'srcarch': self.conn.get().getType(), + 'destarch': self.destConn.getType()}) + + if self.conn.get().getType() != self.destConn.getType(): + kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name) + raise MigrationError("KCHVM00035E", {'name': self.name, + 'srchyp':self.conn.get().getType(), + 'desthyp': self.destConn.getType()}) + + dom = self.vm.get_vm(self.name, self.conn) + + state = DOM_STATE_MAP[dom.info()[0]] + if state == 'migrating': #TODO how to set vm as migrating status ? + kimchi_log.debug('vm is already in migrating %s' % name) + raise MigrationError("KCHVM00036E", {'name': self.name,})
+ elif state != 'running': + kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name) + raise MigrationError("KCHVM00037E", {'name': self.name}) +
Why can't I do cold migration?
+ #check if there is any passthrough devices belong to this vm + vmhostdevs = VMHostDevsModel(conn = self.conn) + hostdevs = vmhostdevs.get_list(self.name) + if hostdevs: + raise MigrationError("KCHVM00038E", {'name' : name, + 'hostdevs': hostdevs}) + + if self.get_vm(name, destConn): + kimchi_log.debug('migrate vm %s already exist on %s' % (name, destConn.getURI())) + raise MigrationError("KCHVM00039E", {'name' : name, + 'remoteHost': remoteHost}) + + + def _getRemoteConn(self, remoteHost, user): + #open destination connect to migrate + transport = 'ssh' + duri = 'qemu+%s://%s@%s/system' % (transport, + user,remoteHost) + + conn = LibvirtConnection(duri) + destConn = conn.get() + return destConn + + def _preMigrate(self, name): + #can't update VM while in Migration source/destination + kimchi_log.debug('prepare migrate %s' % name) + dom = self.get_vm(name, self.conn) + dom.migrateSetMaxDowntime(0) + + + def _do_migrate(self, name, destConn, remoteHost, cb): + + cb('starting a migration') + kimchi_log.debug('migrate %s start' % name) + + flag = 0 + flag |= (libvirt.VIR_MIGRATE_LIVE | + libvirt.VIR_MIGRATE_PEER2PEER | + libvirt.VIR_MIGRATE_TUNNELLED) + #libvirt.VIR_MIGRATE_ABORT_ON_ERROR) + + try: + self.vm.migrate(destConn, None, None,flag, + None, 0) + except libvirt.libvirtError as e: + kimchi_log.error('migrate %s to %s failed' % (name, + remoteHost)) + self._recover(cb) + raise MigrationError('KCHPOOL0040E', {'err': e.message, + 'name': name}) + finally: + self._finish(name, destConn, cb) + + def migrate(self, name, remoteHost, user, passwd ): + destconn = self._getRemoteConn(remoteHost, user) + + self._preCheck(name, destconn, remoteHost) + self._preMigrate(name) + + task_id = add_task('/vms/%s/migrate' % name, self._do_migrate, self.objstore, + {'name' : name, 'destconn': destconn, 'remoteHost': remoteHost}) + + # Record vm migrate task for future querying + try: + with self.objstore as session: + session.store('migrate', self.name, task_id) + return task_id + except Exception as e: + raise MigrationError('KCHPOOL0037E', {'err': e.message}) + + def _recover(self, name, destconn, cb): + #recover + cb('recover from a failed migration',False) + kimchi_log.debug('recover from a failed migrate %s' % name) + dom = self.get_vm(name, self.conn) + state = DOM_STATE_MAP[dom.info()[0]] + if state == 'paused':
Does it mean the VM will be paused during migration? My concern is because Kimchi does not deal well with paused state.
+ try: + dom.resume() + except libvirt.libvirtError as e: + raise OperationFailed("KCHVM0041E", + {'name': name, 'err': e.get_error_message()}) + try: + destvm = self.get_vm(name, destconn) + destvm.destroy() + kimchi_log.error("destory migrate vm on destination server") + #destory and undefine + except libvirt.libvirtError as e: + raise MigrationError('KCHPOOL0042E', {'name' : name, + 'err': e.message}) + + def _finish(self, name, destConnc, cb): + kimchi_log.debug('finished migrate %s' % name) + try: + destConnc.close() + except libvirt.libvirtError as e: + raise MigrationError('KCHPOOL0043E', {'name' : name, + 'remoteuri' : destConnc.getURI(), + 'err': e.message}) + finally: + cb('Migrate finished', True)
def poweroff(self, name): dom = self.get_vm(name, self.conn)

于 2014年11月04日 03:29, Aline Manera 写道:
On 11/03/2014 12:12 PM, Simon Jin wrote:
Signed-off-by: Simon Jin <simonjin@linux.vnet.ibm.com> --- docs/API.md | 3 +- src/kimchi/control/vms.py | 1 + src/kimchi/exception.py | 3 + src/kimchi/i18n.py | 11 ++++ src/kimchi/model/vms.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 1 deletion(-)
diff --git a/docs/API.md b/docs/API.md index 92fbbd5..28dbc6e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -132,7 +132,8 @@ the following general conventions: It emulates the power reset button on a machine. Note that there is a risk of data loss caused by reset without the guest OS shutdown. * connect: Prepare the connection for spice or vnc - +* migrate: Migrate a VM to remote server, only support live mode with out block migration. +
As the "migrate" operation will require some data like host, username and password, you need to describe them too. You can see the POST request to have an idea on how to do it.
OK
### Sub-resource: Virtual Machine Screenshot
**URI:** /vms/*:name*/screenshot diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index 88d8a81..64b88ad 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -42,6 +42,7 @@ class VM(Resource): for ident, node in sub_nodes.items(): setattr(self, ident, node(model, self.ident)) self.start = self.generate_action_handler('start')
+ self.migrate = self.generate_action_handler('migrate', 'remoteHost', 'user', 'passwd')
Migration will take some time to complete so it is better to use self.generate_action_handler_task() which will expect a Task instead of a new resource. Also self.generate_action_handler*() accepts only one parameter which is the action name.
Example: self.generate_action_handler_task('migrate')
OK.
All the other parameters will come on a dict when hits the model code. So on model/vms.py you will have;
def migrate(self, name, params):
And "params" will have all the information you need: host, username and password.
action_args should be a list/tuple, so the code should be: self.migrate = self.generate_action_handler('migrate', ['remoteHost', 'user', 'passwd']), other example is in src/kimchi/control/storagevolumes.py: self.resize = self.generate_action_handler('resize', ['size']) def generate_action_handler(self, action_name, action_args=None): def wrapper(*args, **kwargs): validate_method(('POST'), self.role_key, self.admin_methods) try: self.lookup() if not self.is_authorized(): raise UnauthorizedError('KCHAPI0009E') model_args = list(self.model_args) if action_args is not None: request = parse_request() model_args.extend(request[key] for key in action_args) fn = getattr(self.model, model_fn(self, action_name)) ident = fn(*model_args)
self.poweroff = self.generate_action_handler('poweroff') self.shutdown = self.generate_action_handler('shutdown') self.reset = self.generate_action_handler('reset') diff --git a/src/kimchi/exception.py b/src/kimchi/exception.py index 039152a..732b3ee 100644 --- a/src/kimchi/exception.py +++ b/src/kimchi/exception.py @@ -100,3 +100,6 @@ class TimeoutExpired(KimchiException):
class UnauthorizedError(KimchiException): pass
+ +class MigrationError(KimchiException): + pass
You don't need to create a new type of error. You can use OperationFailed
OK
\ No newline at end of file diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 75fb076..2fd1073 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -101,6 +101,17 @@ messages = { "KCHVM0030E": _("Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"), "KCHVM0031E": _("The guest console password must be a string."), "KCHVM0032E": _("The life time for the guest console password must be a number."), + "KCHVM0033E": _("Can't migrate to localhost."),
+ "KCHVM0034E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."), X86 --> PPC + "KCHVM0035E": _("Can't migrate from %(srchyp)s to a different Hypervisor type %(desthyp)s."),
Qemu/kvm --> Xen
What is the difference between those 2 messages?
+ "KCHVM0036E": _("Can't migrate vm %(name)s,which is already in migrating state."), + "KCHVM0037E": _("Can't migrate vm %(name)s,which isn't in running state."),
Does it mean I can not migrate a powered off vm?
a powered off vm can be migrated with --offline, will add this support in v2.
+ "KCHVM0038E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)s."), + "KCHVM0039E": _("Can't migrate vm %(name)s, vm already exist on destination server %(remoteHost)s."), + "KCHVM0040E": _("Failed Migrate vm %(name)s due error: %(err)s"), + "KCHVM0041E": _("Failed resume vm %(name)s from migration recover. Details: %(err)s"), + "KCHVM0042E": _("Failed destory vm %(name)s from migration recover. Details: %(err)s"), + "KCHVM0043E": _("Failed disconnect %(remoteuri)s when finish migrate vm %(name)s. Details: %(err)s"),
"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."), diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 1089464..1c4eef0 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -44,6 +44,11 @@ from kimchi.utils import import_class, kimchi_log, run_setfacl_set_attr from kimchi.utils import template_name_from_uri from kimchi.xmlutils.utils import xpath_get_text, xml_item_update
+from kimchi.model.libvirtconnection import LibvirtConnection +from kimchi.utils import add_task +from kimchi.model.templates import TemplateModel +from kimchi.model.vmhostdevs import VMHostDevsModel +from kimchi.exception import MigrationError
The imports must be in alphabetic order.
ok
DOM_STATE_MAP = {0: 'nostate', 1: 'running', @@ -507,6 +512,139 @@ class VMModel(object): except libvirt.libvirtError as e: raise OperationFailed("KCHVM0019E", {'name': name, 'err': e.get_error_message()}) + + def _preCheck(self, name, destConn, remoteHost): + kimchi_log.debug('precheck migrate %s' % name) + + if remoteHost == 'localhost' or '127.0.0.1': + kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name) + raise MigrationError("KCHVM00033E", {'name': name, + 'remoteHost': remoteHost}) + + + if self.conn.get().getInfo()[0] != destConn.getInfo()[0]: + kimchi_log.debug('vm %s can not migrate to different arch server.' % (name, destConn.getInfo()[0])) + raise MigrationError("KCHVM00034E", {'name': name, + 'srcarch': self.conn.get().getType(), + 'destarch': self.destConn.getType()}) + + if self.conn.get().getType() != self.destConn.getType(): + kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name) + raise MigrationError("KCHVM00035E", {'name': self.name, + 'srchyp':self.conn.get().getType(), + 'desthyp': self.destConn.getType()}) + + dom = self.vm.get_vm(self.name, self.conn) + + state = DOM_STATE_MAP[dom.info()[0]] + if state == 'migrating': #TODO how to set vm as migrating status ? + kimchi_log.debug('vm is already in migrating %s' % name) + raise MigrationError("KCHVM00036E", {'name': self.name,})
+ elif state != 'running': + kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name) + raise MigrationError("KCHVM00037E", {'name': self.name}) +
Why can't I do cold migration?
+ #check if there is any passthrough devices belong to this vm + vmhostdevs = VMHostDevsModel(conn = self.conn) + hostdevs = vmhostdevs.get_list(self.name) + if hostdevs: + raise MigrationError("KCHVM00038E", {'name' : name, + 'hostdevs': hostdevs}) + + if self.get_vm(name, destConn): + kimchi_log.debug('migrate vm %s already exist on %s' % (name, destConn.getURI())) + raise MigrationError("KCHVM00039E", {'name' : name, + 'remoteHost': remoteHost}) + + + def _getRemoteConn(self, remoteHost, user): + #open destination connect to migrate + transport = 'ssh' + duri = 'qemu+%s://%s@%s/system' % (transport, + user,remoteHost) + + conn = LibvirtConnection(duri) + destConn = conn.get() + return destConn + + def _preMigrate(self, name): + #can't update VM while in Migration source/destination + kimchi_log.debug('prepare migrate %s' % name) + dom = self.get_vm(name, self.conn) + dom.migrateSetMaxDowntime(0) + + + def _do_migrate(self, name, destConn, remoteHost, cb): + + cb('starting a migration') + kimchi_log.debug('migrate %s start' % name) + + flag = 0 + flag |= (libvirt.VIR_MIGRATE_LIVE | + libvirt.VIR_MIGRATE_PEER2PEER | + libvirt.VIR_MIGRATE_TUNNELLED) + #libvirt.VIR_MIGRATE_ABORT_ON_ERROR) + + try: + self.vm.migrate(destConn, None, None,flag, + None, 0) + except libvirt.libvirtError as e: + kimchi_log.error('migrate %s to %s failed' % (name, + remoteHost)) + self._recover(cb) + raise MigrationError('KCHPOOL0040E', {'err': e.message, + 'name': name}) + finally: + self._finish(name, destConn, cb) + + def migrate(self, name, remoteHost, user, passwd ): + destconn = self._getRemoteConn(remoteHost, user) + + self._preCheck(name, destconn, remoteHost) + self._preMigrate(name) + + task_id = add_task('/vms/%s/migrate' % name, self._do_migrate, self.objstore, + {'name' : name, 'destconn': destconn, 'remoteHost': remoteHost}) + + # Record vm migrate task for future querying + try: + with self.objstore as session: + session.store('migrate', self.name, task_id) + return task_id + except Exception as e: + raise MigrationError('KCHPOOL0037E', {'err': e.message}) + + def _recover(self, name, destconn, cb): + #recover + cb('recover from a failed migration',False) + kimchi_log.debug('recover from a failed migrate %s' % name) + dom = self.get_vm(name, self.conn) + state = DOM_STATE_MAP[dom.info()[0]] + if state == 'paused':
Does it mean the VM will be paused during migration? will delete this.
-Simon
My concern is because Kimchi does not deal well with paused state.
+ try: + dom.resume() + except libvirt.libvirtError as e: + raise OperationFailed("KCHVM0041E", + {'name': name, 'err': e.get_error_message()}) + try: + destvm = self.get_vm(name, destconn) + destvm.destroy() + kimchi_log.error("destory migrate vm on destination server") + #destory and undefine + except libvirt.libvirtError as e: + raise MigrationError('KCHPOOL0042E', {'name' : name, + 'err': e.message}) + + def _finish(self, name, destConnc, cb): + kimchi_log.debug('finished migrate %s' % name) + try: + destConnc.close() + except libvirt.libvirtError as e: + raise MigrationError('KCHPOOL0043E', {'name' : name, + 'remoteuri' : destConnc.getURI(), + 'err': e.message}) + finally: + cb('Migrate finished', True)
def poweroff(self, name): dom = self.get_vm(name, self.conn)

On 11/04/2014 07:41 AM, simonjin wrote:
于 2014年11月04日 03:29, Aline Manera 写道:
On 11/03/2014 12:12 PM, Simon Jin wrote:
Signed-off-by: Simon Jin <simonjin@linux.vnet.ibm.com> --- docs/API.md | 3 +- src/kimchi/control/vms.py | 1 + src/kimchi/exception.py | 3 + src/kimchi/i18n.py | 11 ++++ src/kimchi/model/vms.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 1 deletion(-)
diff --git a/docs/API.md b/docs/API.md index 92fbbd5..28dbc6e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -132,7 +132,8 @@ the following general conventions: It emulates the power reset button on a machine. Note that there is a risk of data loss caused by reset without the guest OS shutdown. * connect: Prepare the connection for spice or vnc - +* migrate: Migrate a VM to remote server, only support live mode with out block migration. +
As the "migrate" operation will require some data like host, username and password, you need to describe them too. You can see the POST request to have an idea on how to do it.
OK
### Sub-resource: Virtual Machine Screenshot
**URI:** /vms/*:name*/screenshot diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index 88d8a81..64b88ad 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -42,6 +42,7 @@ class VM(Resource): for ident, node in sub_nodes.items(): setattr(self, ident, node(model, self.ident)) self.start = self.generate_action_handler('start')
+ self.migrate = self.generate_action_handler('migrate', 'remoteHost', 'user', 'passwd')
Migration will take some time to complete so it is better to use self.generate_action_handler_task() which will expect a Task instead of a new resource. Also self.generate_action_handler*() accepts only one parameter which is the action name.
Example: self.generate_action_handler_task('migrate')
OK.
All the other parameters will come on a dict when hits the model code. So on model/vms.py you will have;
def migrate(self, name, params):
And "params" will have all the information you need: host, username and password.
action_args should be a list/tuple, so the code should be: self.migrate = self.generate_action_handler('migrate', ['remoteHost', 'user', 'passwd']), other example is in src/kimchi/control/storagevolumes.py: self.resize = self.generate_action_handler('resize', ['size'])
def generate_action_handler(self, action_name, action_args=None): def wrapper(*args, **kwargs): validate_method(('POST'), self.role_key, self.admin_methods) try: self.lookup() if not self.is_authorized(): raise UnauthorizedError('KCHAPI0009E')
model_args = list(self.model_args) if action_args is not None: request = parse_request() model_args.extend(request[key] for key in action_args) fn = getattr(self.model, model_fn(self, action_name)) ident = fn(*model_args)
Sorry about the mistake! You are right!
self.poweroff = self.generate_action_handler('poweroff') self.shutdown = self.generate_action_handler('shutdown') self.reset = self.generate_action_handler('reset') diff --git a/src/kimchi/exception.py b/src/kimchi/exception.py index 039152a..732b3ee 100644 --- a/src/kimchi/exception.py +++ b/src/kimchi/exception.py @@ -100,3 +100,6 @@ class TimeoutExpired(KimchiException):
class UnauthorizedError(KimchiException): pass
+ +class MigrationError(KimchiException): + pass
You don't need to create a new type of error. You can use OperationFailed
OK
\ No newline at end of file diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 75fb076..2fd1073 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -101,6 +101,17 @@ messages = { "KCHVM0030E": _("Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"), "KCHVM0031E": _("The guest console password must be a string."), "KCHVM0032E": _("The life time for the guest console password must be a number."), + "KCHVM0033E": _("Can't migrate to localhost."),
+ "KCHVM0034E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."), X86 --> PPC + "KCHVM0035E": _("Can't migrate from %(srchyp)s to a different Hypervisor type %(desthyp)s."),
Qemu/kvm --> Xen
What is the difference between those 2 messages?
+ "KCHVM0036E": _("Can't migrate vm %(name)s,which is already in migrating state."), + "KCHVM0037E": _("Can't migrate vm %(name)s,which isn't in running state."),
Does it mean I can not migrate a powered off vm?
a powered off vm can be migrated with --offline, will add this support in v2.
+ "KCHVM0038E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)s."), + "KCHVM0039E": _("Can't migrate vm %(name)s, vm already exist on destination server %(remoteHost)s."), + "KCHVM0040E": _("Failed Migrate vm %(name)s due error: %(err)s"), + "KCHVM0041E": _("Failed resume vm %(name)s from migration recover. Details: %(err)s"), + "KCHVM0042E": _("Failed destory vm %(name)s from migration recover. Details: %(err)s"), + "KCHVM0043E": _("Failed disconnect %(remoteuri)s when finish migrate vm %(name)s. Details: %(err)s"),
"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."), diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 1089464..1c4eef0 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -44,6 +44,11 @@ from kimchi.utils import import_class, kimchi_log, run_setfacl_set_attr from kimchi.utils import template_name_from_uri from kimchi.xmlutils.utils import xpath_get_text, xml_item_update
+from kimchi.model.libvirtconnection import LibvirtConnection +from kimchi.utils import add_task +from kimchi.model.templates import TemplateModel +from kimchi.model.vmhostdevs import VMHostDevsModel +from kimchi.exception import MigrationError
The imports must be in alphabetic order.
ok
DOM_STATE_MAP = {0: 'nostate', 1: 'running', @@ -507,6 +512,139 @@ class VMModel(object): except libvirt.libvirtError as e: raise OperationFailed("KCHVM0019E", {'name': name, 'err': e.get_error_message()}) + + def _preCheck(self, name, destConn, remoteHost): + kimchi_log.debug('precheck migrate %s' % name) + + if remoteHost == 'localhost' or '127.0.0.1': + kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name) + raise MigrationError("KCHVM00033E", {'name': name, + 'remoteHost': remoteHost}) + + + if self.conn.get().getInfo()[0] != destConn.getInfo()[0]: + kimchi_log.debug('vm %s can not migrate to different arch server.' % (name, destConn.getInfo()[0])) + raise MigrationError("KCHVM00034E", {'name': name, + 'srcarch': self.conn.get().getType(), + 'destarch': self.destConn.getType()}) + + if self.conn.get().getType() != self.destConn.getType(): + kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name) + raise MigrationError("KCHVM00035E", {'name': self.name, + 'srchyp':self.conn.get().getType(), + 'desthyp': self.destConn.getType()}) + + dom = self.vm.get_vm(self.name, self.conn) + + state = DOM_STATE_MAP[dom.info()[0]] + if state == 'migrating': #TODO how to set vm as migrating status ? + kimchi_log.debug('vm is already in migrating %s' % name) + raise MigrationError("KCHVM00036E", {'name': self.name,})
+ elif state != 'running': + kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name) + raise MigrationError("KCHVM00037E", {'name': self.name}) +
Why can't I do cold migration?
+ #check if there is any passthrough devices belong to this vm + vmhostdevs = VMHostDevsModel(conn = self.conn) + hostdevs = vmhostdevs.get_list(self.name) + if hostdevs: + raise MigrationError("KCHVM00038E", {'name' : name, + 'hostdevs': hostdevs}) + + if self.get_vm(name, destConn): + kimchi_log.debug('migrate vm %s already exist on %s' % (name, destConn.getURI())) + raise MigrationError("KCHVM00039E", {'name' : name, + 'remoteHost': remoteHost}) + + + def _getRemoteConn(self, remoteHost, user): + #open destination connect to migrate + transport = 'ssh' + duri = 'qemu+%s://%s@%s/system' % (transport, + user,remoteHost) + + conn = LibvirtConnection(duri) + destConn = conn.get() + return destConn + + def _preMigrate(self, name): + #can't update VM while in Migration source/destination + kimchi_log.debug('prepare migrate %s' % name) + dom = self.get_vm(name, self.conn) + dom.migrateSetMaxDowntime(0) + + + def _do_migrate(self, name, destConn, remoteHost, cb): + + cb('starting a migration') + kimchi_log.debug('migrate %s start' % name) + + flag = 0 + flag |= (libvirt.VIR_MIGRATE_LIVE | + libvirt.VIR_MIGRATE_PEER2PEER | + libvirt.VIR_MIGRATE_TUNNELLED) + #libvirt.VIR_MIGRATE_ABORT_ON_ERROR) + + try: + self.vm.migrate(destConn, None, None,flag, + None, 0) + except libvirt.libvirtError as e: + kimchi_log.error('migrate %s to %s failed' % (name, + remoteHost)) + self._recover(cb) + raise MigrationError('KCHPOOL0040E', {'err': e.message, + 'name': name}) + finally: + self._finish(name, destConn, cb) + + def migrate(self, name, remoteHost, user, passwd ): + destconn = self._getRemoteConn(remoteHost, user) + + self._preCheck(name, destconn, remoteHost) + self._preMigrate(name) + + task_id = add_task('/vms/%s/migrate' % name, self._do_migrate, self.objstore, + {'name' : name, 'destconn': destconn, 'remoteHost': remoteHost}) + + # Record vm migrate task for future querying + try: + with self.objstore as session: + session.store('migrate', self.name, task_id) + return task_id + except Exception as e: + raise MigrationError('KCHPOOL0037E', {'err': e.message}) + + def _recover(self, name, destconn, cb): + #recover + cb('recover from a failed migration',False) + kimchi_log.debug('recover from a failed migrate %s' % name) + dom = self.get_vm(name, self.conn) + state = DOM_STATE_MAP[dom.info()[0]] + if state == 'paused':
Does it mean the VM will be paused during migration? will delete this.
-Simon
My concern is because Kimchi does not deal well with paused state.
+ try: + dom.resume() + except libvirt.libvirtError as e: + raise OperationFailed("KCHVM0041E", + {'name': name, 'err': e.get_error_message()}) + try: + destvm = self.get_vm(name, destconn) + destvm.destroy() + kimchi_log.error("destory migrate vm on destination server") + #destory and undefine + except libvirt.libvirtError as e: + raise MigrationError('KCHPOOL0042E', {'name' : name, + 'err': e.message}) + + def _finish(self, name, destConnc, cb): + kimchi_log.debug('finished migrate %s' % name) + try: + destConnc.close() + except libvirt.libvirtError as e: + raise MigrationError('KCHPOOL0043E', {'name' : name, + 'remoteuri' : destConnc.getURI(), + 'err': e.message}) + finally: + cb('Migrate finished', True)
def poweroff(self, name): dom = self.get_vm(name, self.conn)
participants (3)
-
Aline Manera
-
Simon Jin
-
simonjin