[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