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

simonjin simonjin at linux.vnet.ibm.com
Tue Nov 11 05:27:10 UTC 2014


于 2014年11月11日 12:37, simonjin at linux.vnet.ibm.com 写道:
> 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
The issue is caused by lunch kimchid with sudo, and ssh will then use 
ssh key under 'simon' but the real
userid is root, like simon: sudo ssh root at 9.115.122.75

virt-manager use libvirt.openAuth to pass password which from user input 
to libvir like below, but it doesn't work well as expected, the _auth_cb 
doesn't get called:
import libvirt
import os

def open(_open_uri, passwordcb):
         def _auth_cb(creds, (passwordcb, passwordcreds)):
                 for cred in creds:
                     if cred[0] not in passwordcreds:
                         raise RuntimeError("Unknown cred type '%s', 
expected only "
                                    "%s" % (cred[0], passwordcreds))
                 return passwordcb(creds)

         open_flags = 0
         valid_auth_options = [libvirt.VIR_CRED_AUTHNAME,
                               libvirt.VIR_CRED_PASSPHRASE]

         authcb = _auth_cb
         authcb_data = passwordcb

         conn = libvirt.openAuth(_open_uri,
                     [valid_auth_options, authcb,
                     (authcb_data, valid_auth_options)],
                     open_flags)

         print conn.getInfo()
         print conn.listDomainsID()
         return conn


conn = open('qemu+ssh://simon@9.115.122.75/system','passwd')


> 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)




More information about the Kimchi-devel mailing list