[Kimchi-devel] [v1 1/1] Migrate virtual machines

Aline Manera alinefm at linux.vnet.ibm.com
Mon Nov 3 19:29:58 UTC 2014


On 11/03/2014 12:12 PM, Simon Jin wrote:
> Signed-off-by: Simon Jin <simonjin at linux.vnet.ibm.com>
> ---
>   docs/API.md               |   3 +-
>   src/kimchi/control/vms.py |   1 +
>   src/kimchi/exception.py   |   3 +
>   src/kimchi/i18n.py        |  11 ++++
>   src/kimchi/model/vms.py   | 138 ++++++++++++++++++++++++++++++++++++++++++++++
>   5 files changed, 155 insertions(+), 1 deletion(-)
>
> diff --git a/docs/API.md b/docs/API.md
> index 92fbbd5..28dbc6e 100644
> --- a/docs/API.md
> +++ b/docs/API.md
> @@ -132,7 +132,8 @@ 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 VM to remote server, only support live mode with out block migration.
> +

As the "migrate" operation will require some data like host, username 
and password, you need to describe them too.
You can see the POST request to have an idea on how to do it.

>   
>   ### Sub-resource: Virtual Machine Screenshot
>
>   **URI:** /vms/*:name*/screenshot
> diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
> index 88d8a81..64b88ad 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('migrate', 'remoteHost', 'user', 'passwd')

Migration will take some time to complete so it is better to use 
self.generate_action_handler_task() which will expect a Task instead of 
a new resource.
Also self.generate_action_handler*() accepts only one parameter which is 
the action name.

Example: self.generate_action_handler_task('migrate')

All the other parameters will come on a dict when hits the model code.
So on model/vms.py you will have;

def migrate(self, name, params):

And "params" will have all the information you need: host, username and 
password.

>           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/exception.py b/src/kimchi/exception.py
> index 039152a..732b3ee 100644
> --- a/src/kimchi/exception.py
> +++ b/src/kimchi/exception.py
> @@ -100,3 +100,6 @@ class TimeoutExpired(KimchiException):
>
>   class UnauthorizedError(KimchiException):
>       pass

> +
> +class MigrationError(KimchiException):
> +    pass

You don't need to create a new type of error.
You can use OperationFailed

> \ No newline at end of file
> diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
> index 75fb076..2fd1073 100644
> --- a/src/kimchi/i18n.py
> +++ b/src/kimchi/i18n.py
> @@ -101,6 +101,17 @@ 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."),

What is the difference between those 2 messages?

> +    "KCHVM0036E": _("Can't migrate vm %(name)s,which is already in migrating state."),
> +    "KCHVM0037E": _("Can't migrate vm %(name)s,which isn't in running state."),

Does it mean I can not migrate a powered off vm?

> +    "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 1089464..1c4eef0 100644
> --- a/src/kimchi/model/vms.py
> +++ b/src/kimchi/model/vms.py
> @@ -44,6 +44,11 @@ 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

> +from kimchi.model.libvirtconnection import LibvirtConnection
> +from kimchi.utils import add_task
> +from kimchi.model.templates import TemplateModel
> +from kimchi.model.vmhostdevs import VMHostDevsModel
> +from kimchi.exception import MigrationError

The imports must be in alphabetic order.

>   DOM_STATE_MAP = {0: 'nostate',
>                    1: 'running',
> @@ -507,6 +512,139 @@ 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)
> +
> +        if  remoteHost == 'localhost' or '127.0.0.1':
> +            kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name)
> +            raise MigrationError("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 MigrationError("KCHVM00034E", {'name': name,
> +                                          'srcarch': self.conn.get().getType(),
> +                                          'destarch': self.destConn.getType()})
> +
> +        if self.conn.get().getType() != self.destConn.getType():
> +            kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name)
> +            raise MigrationError("KCHVM00035E", {'name': self.name,
> +                                            'srchyp':self.conn.get().getType(),
> +                                            'desthyp': self.destConn.getType()})
> +
> +        dom = self.vm.get_vm(self.name, self.conn)
> +
> +        state = DOM_STATE_MAP[dom.info()[0]]
> +        if state == 'migrating': #TODO how to set vm as migrating status ?
> +            kimchi_log.debug('vm is already in migrating %s' % name)
> +            raise MigrationError("KCHVM00036E", {'name': self.name,})

> +        elif state != 'running':
> +            kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name)
> +            raise MigrationError("KCHVM00037E", {'name': self.name})
> +

Why can't I do cold migration?

> +        #check if there is any passthrough devices belong to this vm
> +        vmhostdevs = VMHostDevsModel(conn = self.conn)
> +        hostdevs = vmhostdevs.get_list(self.name)
> +        if hostdevs:
> +            raise MigrationError("KCHVM00038E", {'name' : name,
> +                                                 'hostdevs': hostdevs})
> +
> +        if self.get_vm(name, destConn):
> +                kimchi_log.debug('migrate vm %s already exist on %s' % (name, destConn.getURI()))
> +                raise MigrationError("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)
> +        destConn = conn.get()
> +        return destConn
> +
> +    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)
> +        dom.migrateSetMaxDowntime(0)
> +
> +
> +    def _do_migrate(self, name, destConn, remoteHost, cb):
> +
> +        cb('starting a migration')
> +        kimchi_log.debug('migrate %s start' % name)
> +
> +        flag = 0
> +        flag |= (libvirt.VIR_MIGRATE_LIVE |
> +                libvirt.VIR_MIGRATE_PEER2PEER |
> +                libvirt.VIR_MIGRATE_TUNNELLED)
> +                #libvirt.VIR_MIGRATE_ABORT_ON_ERROR)
> +
> +        try:
> +            self.vm.migrate(destConn, None, None,flag,
> +                None, 0)
> +        except libvirt.libvirtError as e:
> +            kimchi_log.error('migrate %s to %s failed' % (name,
> +                                                          remoteHost))
> +            self._recover(cb)
> +            raise MigrationError('KCHPOOL0040E', {'err': e.message,
> +                                                   'name': name})
> +        finally:
> +            self._finish(name, destConn, cb)
> +
> +    def migrate(self, name, remoteHost, user, passwd ):
> +        destconn = self._getRemoteConn(remoteHost, user)
> +
> +        self._preCheck(name, destconn, remoteHost)
> +        self._preMigrate(name)
> +
> +        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', self.name, task_id)
> +            return task_id
> +        except Exception as e:
> +            raise MigrationError('KCHPOOL0037E', {'err': e.message})
> +
> +    def _recover(self, name, destconn, cb):
> +        #recover
> +        cb('recover from a failed migration',False)
> +        kimchi_log.debug('recover from a failed migrate %s' % name)
> +        dom = self.get_vm(name, self.conn)
> +        state = DOM_STATE_MAP[dom.info()[0]]
> +        if state == 'paused':

Does it mean the VM will be paused during migration?
My concern is because Kimchi does not deal well with paused state.

> +            try:
> +                dom.resume()
> +            except libvirt.libvirtError as e:
> +                raise OperationFailed("KCHVM0041E",
> +                                  {'name': name, 'err': e.get_error_message()})
> +        try:
> +            destvm = self.get_vm(name, destconn)
> +            destvm.destroy()
> +            kimchi_log.error("destory migrate vm on destination server")
> +        #destory and undefine
> +        except libvirt.libvirtError as e:
> +            raise MigrationError('KCHPOOL0042E', {'name' : name,
> +                                                  'err': e.message})
> +
> +    def _finish(self, name, destConnc, cb):
> +        kimchi_log.debug('finished migrate %s' % name)
> +        try:
> +            destConnc.close()
> +        except libvirt.libvirtError as e:
> +            raise MigrationError('KCHPOOL0043E', {'name' : name,
> +                                                  'remoteuri' : destConnc.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