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):