Signed-off-by: Simon Jin <simonjin(a)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