
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