[Kimchi-devel] [v4 1/1] Virtual machine migration

simonjin at linux.vnet.ibm.com simonjin at linux.vnet.ibm.com
Fri Nov 14 09:06:17 UTC 2014


From: Simon Jin <simonjin at linux.vnet.ibm.com>

Signed-off-by: Simon Jin <simonjin at linux.vnet.ibm.com>
---
 docs/API.md               |   5 ++
 src/kimchi/control/vms.py |   1 +
 src/kimchi/i18n.py        |  11 ++++
 src/kimchi/model/vms.py   | 130 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 147 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..6da834a 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -105,6 +105,17 @@ 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": _("Failed to set migrateSetMaxDowntime when migrate vm %(name)s. Details: %(err)s "),
+    "KCHVM0042E": _("Can not migrate vm %(name)s when its in %(state)s state."),
+    "KCHVM0043E": _("Failed Migrate vm %(name)s due error: %(err)s"),
+    "KCHVM0044E": _("Failed to store migrate %(name)s task ID %(task_id)s. error: %(err)s"),
+    "KCHVM0045E": _("Failed to destroy vm %(name)s from migration recover. Details: %(err)s"),
+    "KCHVM0046E": _("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..3243f70 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -35,6 +35,7 @@ from kimchi.config import READONLY_POOL_TYPE
 from kimchi.exception import InvalidOperation, InvalidParameter
 from kimchi.exception import NotFoundError, OperationFailed
 from kimchi.model.config import CapabilitiesModel
+from kimchi.model.libvirtconnection import LibvirtConnection
 from kimchi.model.tasks import TaskModel
 from kimchi.model.templates import TemplateModel
 from kimchi.model.utils import get_vm_name
@@ -790,6 +791,135 @@ 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})
+        try:
+           with self.objstore as session:
+               task_id = session.get('migrate', name)
+               if task_id:
+                   kimchi_log.debug('vm %s is already in migrating.' % name)
+                   raise OperationFailed("KCHVM0037E", {'name': name})
+        except NotFoundError as e:
+           kimchi_log.debug('No migrate on vm %s.' % 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}) 
+    #open destination connect to migrate
+    @staticmethod        
+    def _getRemoteConn(remoteHost, user, passwd, transport = 'ssh'):
+        duri = 'qemu+%s://%s@%s/system' % (transport, user,remoteHost)
+        conn = LibvirtConnection(duri)
+        return conn
+
+    def _do_migrate(self, cb, params):
+        name = params['name']
+        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
+            try:
+                dom.migrateSetMaxDowntime(0)
+            except libvirt.libvirtError as e:
+                raise OperationFailed("KCHVM0041E",
+                                  {'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("KCHVM0042E", {'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._finish(name, destConn, False, cb)
+            self._recover(name, destConn, cb)
+            raise OperationFailed('KCHVM0043E', {'err': e.message,
+                                                 'name': name})
+        self._finish(name, destConn, True, cb)
+
+    def migrate(self, name, remoteHost, user, passwd = None, secure = None):                
+        destConn = self._getRemoteConn(remoteHost, user, passwd)
+        self._preCheck(name, destconn, remoteHost, secure)
+        
+        params = {'name': name, 
+                  'destConn': destConn,
+                  'remoteHost': remoteHost, 
+                  'secure': secure}
+        task_id = add_task('/vms/%s/migrate' % name, self._do_migrate, 
+                                    self.objstore, params)
+
+        # Record vm migrate task for future querying
+        try:
+           with self.objstore as session:
+               session.store('migrate', name, task_id)
+        except Exception as e:
+            raise OperationFailed('KCHVM0044E', {'name': name,
+                                                 'task_id': task_id,
+                                                 'err': e.message}) 
+
+    def _recover(self, name, destconn, cb):
+        destvm = self.get_vm(name, destconn)
+        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('KCHVM0045E', {'name' : name,
+                                                 'err': e.message})
+
+    def _finish(self, name, destConn, success, cb):
+        _destConn = destConn.get()
+        kimchi_log.debug('finished migrate %s' % name)       
+        cb('Migrate finished', success)
+        try:
+            _destConn.close()
+        except libvirt.libvirtError as e:
+            raise OperationFailed('KCHVM0046E', {'name' : name,
+                                                'remoteuri' :_destConn.getURI(),
+                                                'err': e.message})
 
     def poweroff(self, name):
         dom = self.get_vm(name, self.conn)
-- 
1.8.3.1




More information about the Kimchi-devel mailing list