From f634145a1e510551e51cd6f4d9e3862e4a2be670 Mon Sep 17 00:00:00 2001
From: Gary Kotton <gkotton@vmware.com>
Date: Wed, 21 Feb 2018 23:48:13 -0800
Subject: [PATCH] TVD: update port migration for V -> T instances

The patch does the following:
1. set instance vNIC to a common network interface
2. Live migrates to T cluster
3. Updates the instance vNIC to opaque network

Example:
nsxadmin -r ports -o nsx-migrate-v-v3 \
    --property project-id=01dd52ff4c7047f79f6259f916c83790 \
    --property host-moref=host-11 --property respool-moref=resgroup-9 \
    --property datastore-moref=datastore-22 \
     --plugin nsxv3

There is also an option to use net-name. The default here is 'VM Network'

Change-Id: I24d9df3f7a3dbd11dffb86427367b809e2b49409
---
 doc/source/admin_util.rst                     |  2 +-
 vmware_nsx/dvs/dvs.py                         | 74 +++++++++++++++++++
 .../admin/plugins/nsxv3/resources/ports.py    | 37 +++++++---
 3 files changed, 102 insertions(+), 11 deletions(-)

diff --git a/doc/source/admin_util.rst b/doc/source/admin_util.rst
index 7f9b886d9e..f7cb9c51a6 100644
--- a/doc/source/admin_util.rst
+++ b/doc/source/admin_util.rst
@@ -347,7 +347,7 @@ Ports
 
 - Update the VMs ports (all or of a specific project) on the backend after migrating nsx-v -> nsx-v3::
 
-    nsxadmin -r ports -o nsx-migrate-v-v3 (--property project-id=<>)
+    nsxadmin -r ports -o nsx-migrate-v-v3 (--property project-id=<> --property host-moref=<> --property respool-moref=<> --property net-name=<> --property datastore-moref=<>)) --plugin nsxv3
 
 - Migrate exclude ports to use tags::
 
diff --git a/vmware_nsx/dvs/dvs.py b/vmware_nsx/dvs/dvs.py
index bc18caad8d..9b20ec968b 100644
--- a/vmware_nsx/dvs/dvs.py
+++ b/vmware_nsx/dvs/dvs.py
@@ -569,6 +569,27 @@ class VMManager(VCManagerBase):
             LOG.error("Failed to reconfigure VM %(moref)s spec: %(e)s",
                       {'moref': vm_moref.value, 'e': e})
 
+    def _build_vm_spec_update(self, devices):
+        client_factory = self._session.vim.client.factory
+        vm_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
+        vm_spec.deviceChange = [devices]
+        return vm_spec
+
+    def update_vm_interface(self, vm_moref, devices):
+        update_spec = self._build_vm_spec_update(devices)
+        task = self._session.invoke_api(self._session.vim,
+                                        'ReconfigVM_Task',
+                                        vm_moref,
+                                        spec=update_spec)
+        try:
+            self._session.wait_for_task(task)
+            LOG.info("Updated VM moref %(moref)s spec - "
+                     "attached an interface",
+                     {'moref': vm_moref.value})
+        except Exception as e:
+            LOG.error("Failed to reconfigure VM %(moref)s spec: %(e)s",
+                      {'moref': vm_moref.value, 'e': e})
+
     def _build_vm_spec_detach(self, device):
         """Builds the vif detach config spec."""
         # Code inspired by nova: get_network_detach_config_spec
@@ -622,6 +643,59 @@ class VMManager(VCManagerBase):
         if port:
             self._update_port_security_policy(dvs_moref, port, status)
 
+    def update_vm_network(self, device, name='VM Network'):
+        # In order to live migrate need a common network for interfaces
+        client_factory = self._session.vim.client.factory
+        network_spec = client_factory.create('ns0:VirtualDeviceConfigSpec')
+        network_spec.operation = 'edit'
+        backing = client_factory.create(
+                  'ns0:VirtualEthernetCardNetworkBackingInfo')
+        backing.deviceName = name
+        device.backing = backing
+        network_spec.device = device
+        return network_spec
+
+    def update_vm_opaque_spec(self, vif_info, device):
+        """Updates the backing for the VIF spec."""
+        client_factory = self._session.vim.client.factory
+        network_spec = client_factory.create('ns0:VirtualDeviceConfigSpec')
+        network_spec.operation = 'edit'
+        backing = client_factory.create(
+                'ns0:VirtualEthernetCardOpaqueNetworkBackingInfo')
+        backing.opaqueNetworkId = vif_info['nsx_id']
+        backing.opaqueNetworkType = 'nsx.LogicalSwitch'
+        # Configure externalId
+        device.externalId = vif_info['iface_id']
+        device.backing = backing
+        network_spec.device = device
+        return network_spec
+
+    def relocate_vm_spec(self, client_factory, respool_moref=None,
+                         datastore_moref=None, host_moref=None,
+                         disk_move_type="moveAllDiskBackingsAndAllowSharing"):
+        rel_spec = client_factory.create('ns0:VirtualMachineRelocateSpec')
+        if datastore_moref:
+            datastore = vim_util.get_moref(datastore_moref, 'Datastore')
+        else:
+            datastore = None
+        rel_spec.datastore = datastore
+        host = vim_util.get_moref(host_moref, 'HostSystem')
+        rel_spec.host = host
+        res_pool = vim_util.get_moref(respool_moref, 'ResourcePool')
+        rel_spec.pool = res_pool
+        return rel_spec
+
+    def relocate_vm(self, vm_ref, respool_moref=None, datastore_moref=None,
+                    host_moref=None,
+                    disk_move_type="moveAllDiskBackingsAndAllowSharing"):
+        client_factory = self._session.vim.client.factory
+        rel_spec = self.relocate_vm_spec(client_factory, respool_moref,
+                                         datastore_moref, host_moref,
+                                         disk_move_type)
+        task = self._session.invoke_api(self._session.vim, "RelocateVM_Task",
+                                        vm_ref, spec=rel_spec)
+        self._session.wait_for_task(task)
+
 
 class ClusterManager(VCManagerBase):
     """Management class for Cluster related VC tasks."""
diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/ports.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/ports.py
index 237e685f1f..e619296620 100644
--- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/ports.py
+++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/ports.py
@@ -232,6 +232,17 @@ def migrate_compute_ports_vms(resource, event, trigger, **kwargs):
         project = properties.get('project-id')
         if project:
             port_filters['project_id'] = [project]
+        net_name = properties.get('net-name', 'VM Network')
+        LOG.info("Common network name for migration %s", net_name)
+        host_moref = properties.get('host-moref')
+        # TODO(garyk): We can explore the option of passing the cluster
+        # moref then this will remove the need for the host-moref and the
+        # resource pool moref.
+        respool_moref = properties.get('respool-moref')
+        datastore_moref = properties.get('datastore-moref')
+        if not host_moref:
+            LOG.error("Unable to migrate with no host")
+            return
 
     # Go over all the ports from the plugin
     admin_cxt = neutron_context.get_admin_context()
@@ -266,22 +277,28 @@ def migrate_compute_ports_vms(resource, event, trigger, **kwargs):
             LOG.info("No need to update the spec of vm %s", device_id)
             continue
 
-        # find the old interface by it's mac and delete it
         device = get_vm_network_device(vm_mng, vm_moref, port['mac_address'])
         if device is None:
             LOG.warning("No device with MAC address %s exists on the VM",
                         port['mac_address'])
             continue
-        device_type = device.__class__.__name__
 
-        LOG.info("Detaching old interface from VM %s", device_id)
-        vm_mng.detach_vm_interface(vm_moref, device)
-
-        # add the new interface as OpaqueNetwork
-        LOG.info("Attaching new interface to VM %s", device_id)
-        nsx_net_id = get_network_nsx_id(admin_cxt.session, port['network_id'])
-        vm_mng.attach_vm_interface(vm_moref, port['id'], port['mac_address'],
-                                   nsx_net_id, device_type)
+        # Update interface to be common network
+        devices = [vm_mng.update_vm_network(device, name=net_name)]
+        LOG.info("Update instance %s to common network", device_id)
+        vm_mng.update_vm_interface(vm_moref, devices=devices)
+        LOG.info("Migrate instance %s to host %s", device_id, host_moref)
+        vm_mng.relocate_vm(vm_moref, host_moref=host_moref,
+                           datastore_moref=datastore_moref,
+                           respool_moref=respool_moref)
+        LOG.info("Update instance %s to opaque network", device_id)
+        device = get_vm_network_device(vm_mng, vm_moref, port['mac_address'])
+        vif_info = {'nsx_id': get_network_nsx_id(admin_cxt.session,
+                                                 port['network_id']),
+                    'iface_id': port['id']}
+        devices = [vm_mng.update_vm_opaque_spec(vif_info, device)]
+        vm_mng.update_vm_interface(vm_moref, devices=devices)
+        LOG.info("Instance %s successfully migrated!", device_id)
 
 
 def migrate_exclude_ports(resource, event, trigger, **kwargs):