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