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

simonjin at linux.vnet.ibm.com simonjin at linux.vnet.ibm.com
Tue Nov 11 04:37:56 UTC 2014


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

Issue:
Migraton SSH authentication,
even 2 peers have ssh key exchanged, migration still failed due to failed authentication,
first of all, still need passwd input to get a remote libvirt connection
second, after that, migration still failed with error:
libvirt: QEMU Driver error : 操作失败: Failed to connect to remote libvirt URI qemu+ssh://root@9.115.122.75/system: Cannot recv data: Permission denied, please try again.
Permission denied, please try again.
Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).: Connection reset by peer

How to test:
Apply this patch(did some hack to easy debug)
and issue migrate action with:
sudo curl -k -u use:password -H "Content-Type: application/json" -H "Accept: application/json" https://localhost:8001/vms/RHEL-7/migrate -X POST -d '{"remoteHost": "9.115.122.75", "user": "root", "passwd": "passwd"}'

v2:
- use generate_action_handler_task
- Fix args in generate_action_handler_task
- Remove MigrationError
- Put import in alphabetic
- Support offline migration

Signed-off-by: Simon Jin <simonjin at linux.vnet.ibm.com>
---
 docs/API.md               |   4 ++
 src/kimchi/control/vms.py |   1 +
 src/kimchi/i18n.py        |  10 ++++
 src/kimchi/model/vms.py   | 145 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 160 insertions(+)

diff --git a/docs/API.md b/docs/API.md
index 9c06f85..36e5ab6 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -132,6 +132,10 @@ 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.
 
 ### Sub-resource: Virtual Machine Screenshot
 
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index 88d8a81..0ab239e 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'])
         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 f789259..6edc595 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -101,6 +101,16 @@ 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."),
+    "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 f2e4ae3..c6709ca 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -30,16 +30,20 @@ from xml.etree import ElementTree
 import libvirt
 from cherrypy.process.plugins import BackgroundTask
 
+from kimchi.utils import add_task
 from kimchi import 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.templates import TemplateModel
+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.screenshot import VMScreenshot
+#from kimchi.model.vmhostdevs import VMHostDevsModel
 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
@@ -514,6 +518,147 @@ 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 is not in running, only running vm can be migrated' % name)
+            raise OperationFailed("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 OperationFailed("KCHVM00034E", {'name': name,
+                                          'srcarch': self.conn.get().getType(),
+                                          'destarch': _destConn.getType()})  
+
+        if self.conn.get().getType() != _destConn.getType():
+            kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name)
+            raise OperationFailed("KCHVM00035E", {'name': self.name,
+                                            'srchyp':self.conn.get().getType(),
+                                            'desthyp': _destConn.getType()})                
+          
+
+        #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("KCHVM00038E", {'name' : name,
+                                                 'hostdevs': hostdevs}) 
+        #try: #FIXME 
+        #    self.get_vm(name, destConn)    
+        #except NotFoundError as e:
+        #    if e != NotFoundError:
+        #        kimchi_log.debug('migrate vm %s already exist on %s' % (name, destConn.getURI())) 
+                #raise OperationFailed("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)
+        return conn
+                               
+    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)       
+        try:
+            dom.migrateSetMaxDowntime(0)
+        except libvirt.libvirtError as e:
+            print e.message
+            
+    def _do_migrate(self, name, destConn, remoteHost, cb):
+        _destConn = destConn.get()
+        cb('starting a migration')
+        kimchi_log.debug('migrate %s start' % name)
+        #import pdb
+        #pdb.set_trace()
+        dom = self.get_vm(name, self.conn)    
+        flag = 0
+        flag |= (libvirt.VIR_MIGRATE_PEER2PEER |
+                 libvirt.VIR_MIGRATE_TUNNELLED)
+
+        state = DOM_STATE_MAP[dom.info()[0]]
+        if state == 'running':
+            flag |= libvirt.VIR_MIGRATE_LIVE
+            try:
+                dom.migrateSetMaxDowntime(0)
+            except libvirt.libvirtError as e:
+                print e.message #FIXME
+                
+        elif state == 'shutoff':
+            flag |= libvirt.VIR_MIGRATE_OFFLINE
+        else: #FIXME any other state will prevend migration ?
+            kimchi_log.debug('Can not migrate vm %s when its in %s.' % (name, state))
+            raise OperationFailed("KCHVM00037E", {'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('KCHPOOL0040E', {'err': e.message,
+                                                   'name': name})
+        finally:
+            self._finish(name, destConn, cb)      
+
+    def migrate(self, name, remoteHost, user, passwd ):                
+        destconn = self._getRemoteConn(remoteHost, user)
+   
+        flag = self._preCheck(name, destconn, remoteHost)   
+        self._preMigrate(name)
+        def cb(message):
+           print message
+           
+          
+        self._do_migrate(name, destconn, remoteHost,cb)
+        
+        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', 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('KCHPOOL0042E', {'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('KCHPOOL0043E', {'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




More information about the Kimchi-devel mailing list