<html>
  <head>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  </head>
  <body bgcolor="#FFFFFF" text="#000000">
    <br>
    <div class="moz-cite-prefix">在 2014年12月16日 15:08, Royce Lv 写道:<br>
    </div>
    <blockquote cite="mid:548FDA64.9000801@linux.vnet.ibm.com"
      type="cite">
      <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
      <div class="moz-cite-prefix">On 2014年12月16日 10:39, <a
          moz-do-not-send="true" class="moz-txt-link-abbreviated"
          href="mailto:simonjin@linux.vnet.ibm.com">simonjin@linux.vnet.ibm.com</a>
        wrote:<br>
      </div>
      <blockquote
cite="mid:351ac07fffa072d154fe50acf4ab33fbee9d9b43.1418697555.git.simonjin@linux.vnet.ibm.com"
        type="cite">
        <pre wrap="">From: Simon Jin <a moz-do-not-send="true" class="moz-txt-link-rfc2396E" href="mailto:simonjin@linux.vnet.ibm.com">&lt;simonjin@linux.vnet.ibm.com&gt;</a>

v6-v5:Fix pep8, delete migratation finish func.
Signed-off-by: Simon Jin <a moz-do-not-send="true" class="moz-txt-link-rfc2396E" href="mailto:simonjin@linux.vnet.ibm.com">&lt;simonjin@linux.vnet.ibm.com&gt;</a>
---
 docs/API.md               |   5 ++
 src/kimchi/control/vms.py |   3 +
 src/kimchi/i18n.py        |   8 +++
 src/kimchi/model/vms.py   | 166 +++++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 180 insertions(+), 2 deletions(-)

diff --git a/docs/API.md b/docs/API.md
index 210a036..4408764 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -132,6 +132,11 @@ 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.
+    * secure: Migrate in P2P secure tunel mode.(optional)</pre>
      </blockquote>
      Can we add this func to API.json so that parameters can be
      checked?<br>
    </blockquote>
    Will do.<br>
    <blockquote cite="mid:548FDA64.9000801@linux.vnet.ibm.com"
      type="cite">
      <blockquote
cite="mid:351ac07fffa072d154fe50acf4ab33fbee9d9b43.1418697555.git.simonjin@linux.vnet.ibm.com"
        type="cite">
        <pre wrap=""> * clone: Create a new VM identical to this VM. The new VM's name, UUID and
          network MAC addresses will be generated automatically. Each existing
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index a1589ef..3638b3a 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -42,6 +42,9 @@ 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', 'secure'])
         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 e3a051c..81a6dbb 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -107,6 +107,14 @@ messages = {
     "KCHVM0033E": _("Virtual machine '%(name)s' must be stopped before cloning it."),
     "KCHVM0034E": _("Insufficient disk space to clone virtual machine '%(name)s'"),
     "KCHVM0035E": _("Unable to clone VM '%(name)s'. Details: %(err)s"),
+    "KCHVM0036E": _("Migrate %(name)s to localhost %(remoteHost)s is not allowed."),
+    "KCHVM0037E": _("vm %(name)s is already in migrating, can not perform migrate on it again."),
+    "KCHVM0038E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."),
+    "KCHVM0039E": _("Can't migrate %(name)s from %(srchyp)s to a different Hypervisor type %(desthyp)s."),
+    "KCHVM0040E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)s."),
+    "KCHVM0041E": _("Can not migrate vm %(name)s when its in %(state)s state."),
+    "KCHVM0042E": _("Failed Migrate vm %(name)s due error: %(err)s"),
+    "KCHVM0043E": _("Failed to destroy vm %(name)s from migration recover. 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 3aa1145..023bd10 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -33,9 +33,10 @@ from cherrypy.process.plugins import BackgroundTask
 from kimchi import model, vnc
 from kimchi.config import READONLY_POOL_TYPE, config
 from kimchi.exception import InvalidOperation, InvalidParameter
-from kimchi.exception import NotFoundError, OperationFailed
+from kimchi.exception import NotFoundError, OperationFailed, KimchiException</pre>
      </blockquote>
      Why do we need to introduce general error KimchiException?<br>
    </blockquote>
    <i><small>    def _recover(self, name, destconn, cb):<br>
                """Recover from a failed migration.<br>
        <br>
                Arguments:<br>
                "cb": A callback function to signal the Task's progress.<br>
                "name": Name of the virtual machine to be migrated.<br>
                "destConn": A LibvirtConnection to destination server.<br>
                """<br>
                try:<br>
                    destvm = self.get_vm(name, destconn)<br>
                except KimchiException as e:<br>
                    if type(e) == NotFoundError:<br>
                        return</small></i><br>
    <br>
    self.get_vm will raise a kimchiExecption when no vm found, and here
    i need catch it and make _recover <br>
    return since the vm is not exiting on remote server, no need more
    action in recover.<br>
    <br>
    <blockquote cite="mid:548FDA64.9000801@linux.vnet.ibm.com"
      type="cite">
      <blockquote
cite="mid:351ac07fffa072d154fe50acf4ab33fbee9d9b43.1418697555.git.simonjin@linux.vnet.ibm.com"
        type="cite">
        <pre wrap=""> from kimchi.model.config import CapabilitiesModel
-from kimchi.model.tasks import TaskModel
+from kimchi.model.libvirtconnection import LibvirtConnection
+from kimchi.model.tasks import TaskModel, TasksModel
 from kimchi.model.templates import TemplateModel
 from kimchi.model.utils import get_vm_name
 from kimchi.model.utils import get_metadata_node
@@ -268,6 +269,7 @@ class VMModel(object):
         self.groups = import_class('kimchi.model.groups.GroupsModel')(**kargs)
         self.vms = VMsModel(**kargs)
         self.task = TaskModel(**kargs)
+        self.tasks = TasksModel(**kargs)
         self.storagepool = model.storagepools.StoragePoolModel(**kargs)
         self.storagevolume = model.storagevolumes.StorageVolumeModel(**kargs)
         self.storagevolumes = model.storagevolumes.StorageVolumesModel(**kargs)
@@ -850,6 +852,166 @@ class VMModel(object):
             raise OperationFailed("KCHVM0019E",
                                   {'name': name, 'err': e.get_error_message()})

+    def _preCheck(self, name, destConn, remoteHost):
+        """Pre check before migration"""
+
+        kimchi_log.debug('precheck migrate %s' % name)
+        _destConn = destConn.get()
+        if remoteHost in ['localhost', '127.0.0.1']:
+            kimchi_log.debug('vm %s Can not migrate to localhost.' % name)
+            raise OperationFailed("KCHVM0036E", {'name': name,
+                                                 'remoteHost': remoteHost})
+        # check if the vm already in migration
+        try:
+            alltasks = self.tasks.get_list()
+            for taskid in alltasks:
+                taskobj = self.task.lookup(taskid)
+                if taskobj['target_uri'] == "/vms/%s/migrate" % name \
+                        and taskobj['status'] == "running":</pre>
      </blockquote>
      We need to abstract a helper function for this because we use
      target_uri filtering a lot.<br>
    </blockquote>
    Will do.<br>
    <br>
    <blockquote cite="mid:548FDA64.9000801@linux.vnet.ibm.com"
      type="cite"> Besides, I think we need a lock here rather than just
      check the task id, because we enter this part, we are ahead of the
      actual creation of tasks. we may have 2 migrations pass this check
      before both running to "add_task" line.<br>
    </blockquote>
    Migration on UI will be disabled after one migration started.
    <blockquote cite="mid:548FDA64.9000801@linux.vnet.ibm.com"
      type="cite"> Or do we have a libvirt/qemu lock to leverage so that
      we can probe its status?<br>
    </blockquote>
    libvirt doesn't have a field for vm migration status.<br>
    <br>
    <blockquote cite="mid:548FDA64.9000801@linux.vnet.ibm.com"
      type="cite">
      <blockquote
cite="mid:351ac07fffa072d154fe50acf4ab33fbee9d9b43.1418697555.git.simonjin@linux.vnet.ibm.com"
        type="cite">
        <pre wrap="">+                    kimchi_log.debug('vm %s is already in migrating.' % name)
+                    raise OperationFailed("KCHVM0037E", {'name': name})
+        except NotFoundError:
+            kimchi_log.debug('No previous migrate on vm %s.' % name)
+
+        srcarch = self.conn.get().getInfo()[0]
+        destarch = _destConn.getInfo()[0]
+        if srcarch != destarch:
+            kimchi_log.debug('vm %s can not migrate to different arch server.'
+                             % (name, destarch))
+            raise OperationFailed("KCHVM0038E", {'name': name,
+                                                 'srcarch': srcarch,
+                                                 'destarch': destarch})</pre>
      </blockquote>
      AFAIK, these requirements seem not enough to guarantee a
      migration,(see <a moz-do-not-send="true"
        class="moz-txt-link-freetext"
        href="http://wiki.libvirt.org/page/VM_lifecycle#Migration">http://wiki.libvirt.org/page/VM_lifecycle#Migration</a>)<br>
      <meta http-equiv="content-type" content="text/html; charset=utf-8">
      <p style="padding: 0px; margin: 1em 0px; color: rgb(0, 0, 0);
        font-family: Verdana, Arial, Helvetica, sans-serif; font-size:
        13px; font-style: normal; font-variant: normal; font-weight:
        normal; letter-spacing: normal; line-height: normal; orphans:
        auto; text-align: start; text-indent: 0px; text-transform: none;
        white-space: normal; widows: auto; word-spacing: 0px;
        -webkit-text-stroke-width: 0px; background-color: rgb(255, 255,
        255);">Requirements for migration:</p>
      <ul style="padding: 0px; margin: 1em 0px 1em 3em; color: rgb(0, 0,
        0); font-family: Verdana, Arial, Helvetica, sans-serif;
        font-size: 13px; font-style: normal; font-variant: normal;
        font-weight: normal; letter-spacing: normal; line-height:
        normal; orphans: auto; text-align: start; text-indent: 0px;
        text-transform: none; white-space: normal; widows: auto;
        word-spacing: 0px; -webkit-text-stroke-width: 0px;
        background-color: rgb(255, 255, 255);">
        <li>Shared storage accessible under same paths and
          locations,e.g. iSCSI, NFS, etc.</li>
        <li>Exactly the same versions of hypervisor software on both
          physical hosts</li>
        <li>Same network configuration.</li>
        <li>Same CPUs or better. The CPUs must be from the same vendor
          and the CPU flags on the destination host must be superset of
          CPU flags on the source host.</li>
      </ul>
    </blockquote>
    The above check already covered by libvirt/qemu, shall we leave them
    to libvirt/qemu like virt-manage/openstack does ? <br>
    <blockquote cite="mid:548FDA64.9000801@linux.vnet.ibm.com"
      type="cite">
      <ul style="padding: 0px; margin: 1em 0px 1em 3em; color: rgb(0, 0,
        0); font-family: Verdana, Arial, Helvetica, sans-serif;
        font-size: 13px; font-style: normal; font-variant: normal;
        font-weight: normal; letter-spacing: normal; line-height:
        normal; orphans: auto; text-align: start; text-indent: 0px;
        text-transform: none; white-space: normal; widows: auto;
        word-spacing: 0px; -webkit-text-stroke-width: 0px;
        background-color: rgb(255, 255, 255);">
      </ul>
      <blockquote
cite="mid:351ac07fffa072d154fe50acf4ab33fbee9d9b43.1418697555.git.simonjin@linux.vnet.ibm.com"
        type="cite">
        <pre wrap="">+        srchyp = self.conn.get().getType()
+        desthyp = _destConn.getType()
+        if srchyp != desthyp:
+            kimchi_log.debug('vm %s can not migrate to a different hypervisor'
+                             % name)
+            raise OperationFailed("KCHVM0039E", {'name': name,
+                                                 'srchyp': srchyp,
+                                                 'desthyp': desthyp})
+
+        # 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("KCHVM0040E", {'name': name,
+                                  'hostdevs': hostdevs})
+
+    @staticmethod
+    def _getRemoteConn(remoteHost, user, passwd, transport='ssh'):
+        """open destination connect to migrate"""
+
+        duri = 'qemu+%s://%s@%s/system' % (transport, user, remoteHost)
+        conn = LibvirtConnection(duri)
+        return conn
+
+    def _do_migrate(self, cb, params):
+        """Asynchronous function which performs the migrate operation.
+
+        Arguments:
+        cb -- A callback function to signal the Task's progress.
+        params -- A dict with the following values:
+           "name": Name of the virtual machine to be migrated.
+           "destConn": A LibvirtConnection to destination server.
+           "remoteHost": The migration destination server.
+           "secure": Migrate in P2P secure tunel mode.(optional)
+        """
+
+        name = params['name'].decode('utf-8')
+        destConn = params['destConn']
+        remoteHost = params['remoteHost']
+        secure = params['secure']
+        _destConn = destConn.get()</pre>
      </blockquote>
      We can put the destConn.get() to function to _getRemoteConn to
      avoid duplicate code.<br>
    </blockquote>
    _getRemoteConn will return a LibvirtConnection, here we need to get
    a libvirt.virConnect from LibvirtConnection.<br>
    <br>
    -Simon<br>
    <blockquote cite="mid:548FDA64.9000801@linux.vnet.ibm.com"
      type="cite">
      <blockquote
cite="mid:351ac07fffa072d154fe50acf4ab33fbee9d9b43.1418697555.git.simonjin@linux.vnet.ibm.com"
        type="cite">
        <pre wrap="">+
+        cb('starting a migration')
+        kimchi_log.debug('migrate %s start' % name)
+        dom = self.get_vm(name, self.conn)
+        flag = 0
+        if secure:
+            flag |= (libvirt.VIR_MIGRATE_PEER2PEER |
+                     libvirt.VIR_MIGRATE_TUNNELLED)
+
+        state = DOM_STATE_MAP[dom.info()[0]]
+        if state in ['running', 'paused']:
+            flag |= libvirt.VIR_MIGRATE_LIVE
+        elif state == 'shutoff':
+            flag |= (libvirt.VIR_MIGRATE_OFFLINE |
+                     libvirt.VIR_MIGRATE_PERSIST_DEST)
+        else:
+            kimchi_log.debug('Can not migrate vm %s when its in %s.'
+                             % (name, state))
+            raise OperationFailed("KCHVM0041E", {'name': name,
+                                                 'state': state})
+        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(name, destConn, cb)
+            kimchi_log.debug('finish migrate %s' % name)
+            cb('Migrate finished', False)
+            raise OperationFailed('KCHVM0042E', {'err': e.message,
+                                                 'name': name})
+        kimchi_log.debug('finish migrate %s' % name)
+        cb('Migrate finished', True)
+
+    def migrate(self, name, remoteHost, user, passwd=None, secure=False):
+        """Migrate a virtual machine to a remote server.
+
+        Arguments:
+        name -- The name of the virtual machine to be migrated.
+        remoteHost -- Remote server that the VM will be migrated to.
+        user -- Login user on remote server,prefer root.
+        passwd -- The password of the user on remote server,
+        if the local server
+        has ssk key exchanged, then this param can be NULL.
+        secure -- Migrate in P2P secure tunel mode.(optional)
+
+        Return:
+        A Task running the clone operation.
+        """
+        name = name.decode('utf-8')
+        remoteHost = remoteHost.decode('utf-8')
+        user = user.decode('utf-8')
+
+        destConn = self._getRemoteConn(remoteHost, user, passwd)
+        self._preCheck(name, destConn, remoteHost)
+
+        params = {'name': name,
+                  'destConn': destConn,
+                  'remoteHost': remoteHost,
+                  'secure': secure}
+        task_id = add_task('/vms/%s/migrate' % name, self._do_migrate,
+                           self.objstore, params)
+
+        return self.task.lookup(task_id)
+
+    def _recover(self, name, destconn, cb):
+        """Recover from a failed migration.
+
+        Arguments:
+        "cb": A callback function to signal the Task's progress.
+        "name": Name of the virtual machine to be migrated.
+        "destConn": A LibvirtConnection to destination server.
+        """
+        try:
+            destvm = self.get_vm(name, destconn)
+        except KimchiException as e:
+            if type(e) == NotFoundError:
+                return
+
+        cb('recover from a failed migration', False)
+        kimchi_log.debug('recover from a failed migrate %s' % name)
+        try:
+            destvm.destroy()
+            kimchi_log.error("Failed to destory migrate vm %s on \
+                              destination server" % name)
+        except libvirt.libvirtError as e:
+            raise OperationFailed('KCHVM0043E', {'name': name,
+                                                 'err': e.message})
+
     def poweroff(self, name):
         dom = self.get_vm(name, self.conn)
         try:
</pre>
      </blockquote>
      <br>
    </blockquote>
    <br>
  </body>
</html>