From: Simon Jin <simonjin(a)linux.vnet.ibm.com>
v3-v2: address commets from vianac(a)linux.vnet.ibm.com.
Signed-off-by: Simon Jin <simonjin(a)linux.vnet.ibm.com>
---
docs/API.md | 5 ++
src/kimchi/control/vms.py | 1 +
src/kimchi/i18n.py | 9 ++++
src/kimchi/model/vms.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 131 insertions(+)
diff --git a/docs/API.md b/docs/API.md
index 9b866f3..17073c8 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -132,6 +132,11 @@ 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 virtual machine to a remote server, only support live mode without
block migration.
+ * remoteHost: IP address or hostname of the remote server.
+ * user: User of the remote server.
+ * passwd: Passwd of user on remote server.
+ * secure: Migrate in P2P secure tunel mode.
* clone: Create a new VM identical to this VM. The new VM's name, UUID and
network MAC addresses will be generated automatically. Each existing
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index a1589ef..e6870e0 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_task('migrate',
['remoteHost', 'user', 'passwd', 'secure'])
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/i18n.py b/src/kimchi/i18n.py
index e823f2b..8d87671 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -105,6 +105,15 @@ messages = {
"KCHVM0033E": _("Virtual machine '%(name)s' must be stopped
before cloning it."),
"KCHVM0034E": _("Insufficient disk space to clone virtual machine
'%(name)s'"),
"KCHVM0035E": _("Unable to clone VM '%(name)s'. Details:
%(err)s"),
+ "KCHVM0036E": _("Can't migrate to localhost."),
+ "KCHVM0037E": _("Can't migrate vm %(name)s on Hypervisor arch
%(srcarch)s.to a different Hypervisor arch %(destarch)s."),
+ "KCHVM0038E": _("Can't migrate from %(srchyp)s to a different
Hypervisor type %(desthyp)s."),
+ "KCHVM0039E": _("Can't migrate vm %(name)s, vm has passthrough
device %(hostdevs)s."),
+ "KCHVM0040E": _("Failed to set migrateSetMaxDowntime when migrate vm
%(name)s. Details: %(err)s "),
+ "KCHVM0041E": _("Can not migrate vm %(name)s when its in %(state)s
state."),
+ "KCHVM0042E": _("Failed Migrate vm %(name)s due error:
%(err)s"),
+ "KCHVM0043E": _("Failed destory vm %(name)s from migration recover.
Details: %(err)s"),
+ "KCHVM0044E": _("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 d194049..ed8f96f 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -33,6 +33,7 @@ from cherrypy.process.plugins import BackgroundTask
from kimchi import model, vnc
from kimchi.config import READONLY_POOL_TYPE
from kimchi.exception import InvalidOperation, InvalidParameter
+from kimchi.model.libvirtconnection import LibvirtConnection
from kimchi.exception import NotFoundError, OperationFailed
from kimchi.model.config import CapabilitiesModel
from kimchi.model.tasks import TaskModel
@@ -790,6 +791,121 @@ 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)
+
+ _destConn = destConn.get()
+ if remoteHost in ['localhost', '127.0.0.1']:
+ kimchi_log.debug('vm %s Can not migrate to localhost.' % name)
+ raise OperationFailed("KCHVM0036E", {'name': name,
+ 'remoteHost': remoteHost})
+ destarch = _destConn.getInfo()[0]
+ if self.conn.get().getInfo()[0] != destarch:
+ kimchi_log.debug('vm %s can not migrate to different arch server.' %
(name, destarch))
+ raise OperationFailed("KCHVM0037E", {'name': name,
+ 'srcarch':
self.conn.get().getInfo()[0],
+ 'destarch': destarch})
+ desthyp = _destConn.getType()
+ if self.conn.get().getType() != desthyp:
+ kimchi_log.debug('vm %s can not migrate to a different hypervisor' %
name)
+ raise OperationFailed("KCHVM0038E", {'name': self.name,
+ 'srchyp':self.conn.get().getType(),
+ 'desthyp':desthyp})
+
+
+ #model.host import DOM_STATE_MAP, vmhostdevs import host,
+ #to void loop import DOM_STATE_MAP, move import VMHostDevsModel here
+ from kimchi.model.vmhostdevs import VMHostDevsModel
+
+ #check if there is any passthrough devices belong to this vm
+ vmhostdevs = VMHostDevsModel(conn = self.conn)
+ hostdevs = vmhostdevs.get_list(name)
+ if hostdevs:
+ raise OperationFailed("KCHVM0039E", {'name' : name,
+ 'hostdevs': hostdevs})
+ #open destination connect to migrate
+ @staticmethod
+ def _getRemoteConn(self, remoteHost, user, transport = 'ssh'):
+ duri = 'qemu+%s://%s@%s/system' % (transport,
+ user,remoteHost)
+ conn = LibvirtConnection(duri)
+ return conn
+
+ def _do_migrate(self, name, destConn, remoteHost, secure, cb):
+ _destConn = destConn.get()
+
+ cb('starting a migration')
+ kimchi_log.debug('migrate %s start' % name)
+ dom = self.get_vm(name, self.conn)
+ flag = 0
+ if secure:
+ flag |= (libvirt.VIR_MIGRATE_PEER2PEER |
+ libvirt.VIR_MIGRATE_TUNNELLED)
+
+ state = DOM_STATE_MAP[dom.info()[0]]
+ if state in ['running','paused']:
+ flag |= libvirt.VIR_MIGRATE_LIVE
+ try:
+ dom.migrateSetMaxDowntime(0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed("KCHVM0040E",
+ {'name': name, 'err':
e.get_error_message()})
+ elif state == 'shutoff':
+ flag |= libvirt.VIR_MIGRATE_OFFLINE
+ else:
+ kimchi_log.debug('Can not migrate vm %s when its in %s.' % (name,
state))
+ raise OperationFailed("KCHVM0041E", {'name': self.name})
+ try:
+ dom.migrate(_destConn, flag, None, None, 0)
+ except libvirt.libvirtError as e:
+ kimchi_log.error('migrate %s to %s failed' % (name,
+ remoteHost))
+ self._recover(cb)
+ raise OperationFailed('KCHVM0042E', {'err': e.message,
+ 'name': name})
+ finally:
+ self._finish(name, destConn, cb)
+
+ def migrate(self, name, remoteHost, user, passwd = None, secure = None):
+ destconn = self._getRemoteConn(remoteHost, user, passwd)
+ self._preCheck(name, destconn, remoteHost, secure)
+
+ task_id = add_task('/vms/%s/migrate' % name, self._do_migrate,
self.objstore,
+ {'name' : name,
'destconn': destconn,
+ 'remoteHost': remoteHost,
'secure': secure})
+
+ # Record vm migrate task for future querying
+ try:
+ with self.objstore as session:
+ session.store('migrate', name, task_id)
+ return task_id
+ except Exception as e:
+ raise OperationFailed('KCHPOOL0037E', {'err': e.message})
+
+ def _recover(self, name, destconn, cb):
+ destvm = self.get_vm(name, destconn)
+ #recover
+ cb('recover from a failed migration',False)
+ kimchi_log.debug('recover from a failed migrate %s' % name)
+ try:
+ destvm.destroy()
+ kimchi_log.error("destory migrate vm on destination server")
+ except libvirt.libvirtError as e:
+ raise OperationFailed('KCHVM0043E', {'name' : name,
+ 'err': e.message})
+
+ def _finish(self, name, destConn, cb):
+ _destConn = destConn.get()
+ kimchi_log.debug('finished migrate %s' % name)
+ try:
+ _destConn.close()
+ except libvirt.libvirtError as e:
+ raise OperationFailed('KCHVM0044E', {'name' : name,
+ 'remoteuri' :
_destConn.getURI(),
+ 'err': e.message})
+ finally:
+ cb('Migrate finished', True)
def poweroff(self, name):
dom = self.get_vm(name, self.conn)
--
1.8.3.1