[Kimchi-devel] [v7 1/1] Virtual machine migration
simonjin at linux.vnet.ibm.com
simonjin at linux.vnet.ibm.com
Mon Dec 22 08:52:31 UTC 2014
From: Simon Jin <simonjin at linux.vnet.ibm.com>
v7-v6:
Add migrate check in API.json.
Add abstract a helper function task_is_running.
Signed-off-by: Simon Jin <simonjin at linux.vnet.ibm.com>
Signed-off-by: yuntong <yuntongjin at gmail.com>
---
docs/API.md | 5 ++
src/kimchi/API.json | 27 ++++++++
src/kimchi/control/vms.py | 3 +
src/kimchi/i18n.py | 12 ++++
src/kimchi/model/utils.py | 18 ++++++
src/kimchi/model/vms.py | 159 +++++++++++++++++++++++++++++++++++++++++++++-
6 files changed, 222 insertions(+), 2 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index 210a036..349c950 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 *(optional)*: 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/API.json b/src/kimchi/API.json
index c5d7bdb..f6aff5e 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -305,6 +305,33 @@
}
}
},
+ "vm_migrate": {
+ "type": "object",
+ "properties": {
+ "remoteHost": {
+ "description": "IP address or hostname of the remote server",
+ "type": "string",
+ "minLength": 1,
+ "error": "KCHVM0044E"
+ },
+ "user": {
+ "description": "User of the remote server",
+ "type": "string",
+ "minLength": 1,
+ "error": "KCHVM0045E"
+ },
+ "passwd": {
+ "description": "Passwd of user on remote server",
+ "type": "string",
+ "error": "KCHVM0046E"
+ },
+ "secure": {
+ "description": "Migrate in P2P secure tunel mode(optional)",
+ "type": "boolean",
+ "error": "KCHVM0047E"
+ }
+ }
+ },
"networks_create": {
"type": "object",
"error": "KCHNET0016E",
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index a1589ef..3638b3a 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -42,6 +42,9 @@ 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 e3a051c..90eae92 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -107,6 +107,18 @@ 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": _("Migrate %(name)s to localhost %(remoteHost)s is not allowed."),
+ "KCHVM0037E": _("vm %(name)s is already in migrating, can not perform migrate on it again."),
+ "KCHVM0038E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."),
+ "KCHVM0039E": _("Can't migrate %(name)s from %(srchyp)s to a different Hypervisor type %(desthyp)s."),
+ "KCHVM0040E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)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 to destroy vm %(name)s from migration recover. Details: %(err)s"),
+ "KCHVM0044E": _("Migrate remoteHost must be a string"),
+ "KCHVM0045E": _("User name of the remote server must be a string"),
+ "KCHVM0046E": _("The passwd of remote server must be a string"),
+ "KCHVM0047E": _("Migrate secure must be a boolean value."),
"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/utils.py b/src/kimchi/model/utils.py
index 9896289..8c67943 100644
--- a/src/kimchi/model/utils.py
+++ b/src/kimchi/model/utils.py
@@ -24,7 +24,9 @@ from lxml import etree, objectify
from lxml.builder import E, ElementMaker
from kimchi.exception import OperationFailed
+from kimchi.exception import NotFoundError
from kimchi.model.featuretests import FeatureTests
+from kimchi.utils import kimchi_log
KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi"
KIMCHI_NAMESPACE = "kimchi"
@@ -161,3 +163,19 @@ def get_metadata_node(dom, tag, metadata_support, mode="current"):
if node is not None:
return etree.tostring(node)
return ""
+
+
+def task_is_running(tasks, task, target_uri):
+ try:
+ alltasks = tasks.get_list()
+ for taskid in alltasks:
+ taskobj = task.lookup(taskid)
+ if taskobj['target_uri'] == target_uri \
+ and taskobj['status'] == "running":
+ kimchi_log.debug('task with target_uri %s is running.'
+ % target_uri)
+ return True
+ except NotFoundError:
+ kimchi_log.debug('task with target_uri %s is not running.'
+ % target_uri)
+ return False
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index 3aa1145..94412e8 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -33,13 +33,15 @@ from cherrypy.process.plugins import BackgroundTask
from kimchi import model, vnc
from kimchi.config import READONLY_POOL_TYPE, config
from kimchi.exception import InvalidOperation, InvalidParameter
-from kimchi.exception import NotFoundError, OperationFailed
+from kimchi.exception import NotFoundError, OperationFailed, KimchiException
from kimchi.model.config import CapabilitiesModel
-from kimchi.model.tasks import TaskModel
+from kimchi.model.libvirtconnection import LibvirtConnection
+from kimchi.model.tasks import TaskModel, TasksModel
from kimchi.model.templates import TemplateModel
from kimchi.model.utils import get_vm_name
from kimchi.model.utils import get_metadata_node
from kimchi.model.utils import set_metadata_node
+from kimchi.model.utils import task_is_running
from kimchi.rollbackcontext import RollbackContext
from kimchi.screenshot import VMScreenshot
from kimchi.utils import add_task, get_next_clone_name, import_class
@@ -268,6 +270,7 @@ class VMModel(object):
self.groups = import_class('kimchi.model.groups.GroupsModel')(**kargs)
self.vms = VMsModel(**kargs)
self.task = TaskModel(**kargs)
+ self.tasks = TasksModel(**kargs)
self.storagepool = model.storagepools.StoragePoolModel(**kargs)
self.storagevolume = model.storagevolumes.StorageVolumeModel(**kargs)
self.storagevolumes = model.storagevolumes.StorageVolumesModel(**kargs)
@@ -850,6 +853,158 @@ class VMModel(object):
raise OperationFailed("KCHVM0019E",
{'name': name, 'err': e.get_error_message()})
+ def _preCheck(self, name, destConn, remoteHost):
+ """Pre check before migration"""
+
+ 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})
+ # check if the vm already in migration
+ if task_is_running(self.tasks, self.task, "/vms/%s/migrate" % name):
+ raise OperationFailed("KCHVM0037E", {'name': name})
+
+ srcarch = self.conn.get().getInfo()[0]
+ destarch = _destConn.getInfo()[0]
+ if srcarch != destarch:
+ kimchi_log.debug('vm %s can not migrate to different arch server.'
+ % (name, destarch))
+ raise OperationFailed("KCHVM0038E", {'name': name,
+ 'srcarch': srcarch,
+ 'destarch': destarch})
+ srchyp = self.conn.get().getType()
+ desthyp = _destConn.getType()
+ if srchyp != desthyp:
+ kimchi_log.debug('vm %s can not migrate to a different hypervisor'
+ % name)
+ raise OperationFailed("KCHVM0039E", {'name': name,
+ 'srchyp': srchyp,
+ '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("KCHVM0040E", {'name': name,
+ 'hostdevs': hostdevs})
+
+ @staticmethod
+ def _getRemoteConn(remoteHost, user, passwd, transport='ssh'):
+ """open destination connect to migrate"""
+
+ duri = 'qemu+%s://%s@%s/system' % (transport, user, remoteHost)
+ conn = LibvirtConnection(duri)
+ return conn
+
+ def _do_migrate(self, cb, params):
+ """Asynchronous function which performs the migrate operation.
+
+ Arguments:
+ cb -- A callback function to signal the Task's progress.
+ params -- A dict with the following values:
+ "name": Name of the virtual machine to be migrated.
+ "destConn": A LibvirtConnection to destination server.
+ "remoteHost": The migration destination server.
+ "secure": Migrate in P2P secure tunel mode.(optional)
+ """
+
+ name = params['name'].decode('utf-8')
+ destConn = params['destConn']
+ remoteHost = params['remoteHost']
+ secure = params['secure']
+ _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
+ elif state == 'shutoff':
+ flag |= (libvirt.VIR_MIGRATE_OFFLINE |
+ libvirt.VIR_MIGRATE_PERSIST_DEST)
+ else:
+ kimchi_log.debug('Can not migrate vm %s when its in %s.'
+ % (name, state))
+ raise OperationFailed("KCHVM0041E", {'name': name,
+ 'state': state})
+ 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(name, destConn, cb)
+ kimchi_log.debug('finish migrate %s' % name)
+ cb('Migrate finished', False)
+ raise OperationFailed('KCHVM0042E', {'err': e.message,
+ 'name': name})
+ kimchi_log.debug('finish migrate %s' % name)
+ cb('Migrate finished', True)
+
+ def migrate(self, name, remoteHost, user, passwd=None, secure=False):
+ """Migrate a virtual machine to a remote server.
+
+ Arguments:
+ name -- The name of the virtual machine to be migrated.
+ remoteHost -- Remote server that the VM will be migrated to.
+ user -- Login user on remote server,prefer root.
+ passwd -- The password of the user on remote server,
+ if the local server
+ has ssk key exchanged, then this param can be NULL.
+ secure -- Migrate in P2P secure tunel mode.(optional)
+
+ Return:
+ A Task running the clone operation.
+ """
+ name = name.decode('utf-8')
+ remoteHost = remoteHost.decode('utf-8')
+ user = user.decode('utf-8')
+
+ destConn = self._getRemoteConn(remoteHost, user, passwd)
+ self._preCheck(name, destConn, remoteHost)
+
+ params = {'name': name,
+ 'destConn': destConn,
+ 'remoteHost': remoteHost,
+ 'secure': secure}
+ task_id = add_task('/vms/%s/migrate' % name, self._do_migrate,
+ self.objstore, params)
+
+ return self.task.lookup(task_id)
+
+ def _recover(self, name, destconn, cb):
+ """Recover from a failed migration.
+
+ Arguments:
+ "cb": A callback function to signal the Task's progress.
+ "name": Name of the virtual machine to be migrated.
+ "destConn": A LibvirtConnection to destination server.
+ """
+ try:
+ destvm = self.get_vm(name, destconn)
+ except KimchiException as e:
+ if type(e) == NotFoundError:
+ return
+
+ cb('recover from a failed migration', False)
+ kimchi_log.debug('recover from a failed migrate %s' % name)
+ try:
+ destvm.destroy()
+ kimchi_log.error("Failed to destory migrate vm %s on \
+ destination server" % name)
+ except libvirt.libvirtError as e:
+ raise OperationFailed('KCHVM0043E', {'name': name,
+ 'err': e.message})
+
def poweroff(self, name):
dom = self.get_vm(name, self.conn)
try:
--
1.8.3.1
More information about the Kimchi-devel
mailing list