From 353be39d9270de4a0e60143dc184d5897b3b362c Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Wed, 24 Aug 2016 08:38:24 +0300 Subject: [PATCH] [Admin-Utils] NSX-V3 upgrade vm ports after migration After using api_replay to migrate the neutron data from NSX-V to NSX-T we need to update the VM ports to use OpaqueNetwork instead of DistributedVirtualPortgroup Usage: nsxadmin -r ports -o nsx-migrate-v-v3 Output example: Detaching old interface from VM ad02211d-25a1-4e0b-ab6e-ffee48c77077 Updated VM moref vm-59 spec - detached an interface Attaching new interface to VM ad02211d-25a1-4e0b-ab6e-ffee48c77077 Updated VM moref vm-59 spec - attached an interface Change-Id: Ie6b4c929257be9bed9701c9c2073a0e65cab9839 --- doc/source/admin_util.rst | 4 + vmware_nsx/dvs/dvs.py | 121 ++++++++++++++++-- .../admin/plugins/nsxv3/resources/ports.py | 94 +++++++++++++- vmware_nsx/shell/resources.py | 4 +- 4 files changed, 211 insertions(+), 12 deletions(-) diff --git a/doc/source/admin_util.rst b/doc/source/admin_util.rst index 0448f312fd..34d46a34b0 100644 --- a/doc/source/admin_util.rst +++ b/doc/source/admin_util.rst @@ -174,6 +174,10 @@ Ports nsxadmin -r ports -o list-mismatches +- Update the VMs ports on the backend after migrating nsx-v -> nsx-v3 + + nsxadmin -r ports -o nsx-migrate-v-v3 + Security Groups ~~~~~~~~~~~~~~~ diff --git a/vmware_nsx/dvs/dvs.py b/vmware_nsx/dvs/dvs.py index 2d33fabf64..51175d80dd 100644 --- a/vmware_nsx/dvs/dvs.py +++ b/vmware_nsx/dvs/dvs.py @@ -308,7 +308,17 @@ class DvsManager(object): {'net_id': net_id, 'dvs': self._dvs_moref.value}) - def get_vm_moref(self, instance_uuid): + def get_portgroup_info(self, pg_moref): + """Get portgroup information.""" + # Expand the properties to collect on need basis. + properties = ['name'] + pg_info = self._session.invoke_api(vim_util, + 'get_object_properties_dict', + self._session.vim, + pg_moref, properties) + return pg_info + + def get_vm_moref_obj(self, instance_uuid): """Get reference to the VM. The method will make use of FindAllByUuid to get the VM reference. This method finds all VM's on the backend that match the @@ -323,14 +333,105 @@ class DvsManager(object): vmSearch=True, instanceUuid=True) if vm_refs: - return vm_refs[0].value + return vm_refs[0] - def get_portgroup_info(self, pg_moref): - """Get portgroup information.""" - # Expand the properties to collect on need basis. - properties = ['name'] - pg_info = self._session.invoke_api(vim_util, - 'get_object_properties_dict', + def get_vm_moref(self, instance_uuid): + """Get reference to the VM. + """ + vm_ref = self.get_vm_moref_obj(instance_uuid) + if vm_ref: + return vm_ref.value + + def get_vm_spec(self, vm_moref): + vm_spec = self._session.invoke_api(vim_util, + 'get_object_properties', self._session.vim, - pg_moref, properties) - return pg_info + vm_moref, ['network'])[0] + return vm_spec + + def _build_vm_spec_attach(self, neutron_port_id, port_mac, + nsx_net_id, device_type): + # Code inspired by nova: _create_vif_spec + client_factory = self._session.vim.client.factory + vm_spec = client_factory.create('ns0:VirtualMachineConfigSpec') + device_change = client_factory.create('ns0:VirtualDeviceConfigSpec') + device_change.operation = "add" + + net_device = client_factory.create('ns0:' + device_type) + net_device.key = -47 + net_device.addressType = "manual" + # configure the neutron port id and mac + net_device.externalId = neutron_port_id + net_device.macAddress = port_mac + net_device.wakeOnLanEnabled = True + + backing = client_factory.create( + 'ns0:VirtualEthernetCardOpaqueNetworkBackingInfo') + # configure the NSX network Id + backing.opaqueNetworkId = nsx_net_id + backing.opaqueNetworkType = "nsx.LogicalSwitch" + net_device.backing = backing + + connectable_spec = client_factory.create( + 'ns0:VirtualDeviceConnectInfo') + connectable_spec.startConnected = True + connectable_spec.allowGuestControl = True + connectable_spec.connected = True + + net_device.connectable = connectable_spec + + device_change.device = net_device + vm_spec.deviceChange = [device_change] + + return vm_spec + + def attach_vm_interface(self, vm_moref, neutron_port_id, + port_mac, nsx_net_id, device_type): + new_spec = self._build_vm_spec_attach( + neutron_port_id, port_mac, nsx_net_id, device_type) + task = self._session.invoke_api(self._session.vim, + 'ReconfigVM_Task', + vm_moref, + spec=new_spec) + try: + self._session.wait_for_task(task) + LOG.info(_LI("Updated VM moref %(moref)s spec - " + "attached an interface"), + {'moref': vm_moref.value}) + except Exception as e: + LOG.error(_LE("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 + client_factory = self._session.vim.client.factory + config_spec = client_factory.create('ns0:VirtualMachineConfigSpec') + virtual_device_config = client_factory.create( + 'ns0:VirtualDeviceConfigSpec') + virtual_device_config.operation = "remove" + virtual_device_config.device = device + config_spec.deviceChange = [virtual_device_config] + return config_spec + + def detach_vm_interface(self, vm_moref, device): + new_spec = self._build_vm_spec_detach(device) + task = self._session.invoke_api(self._session.vim, + 'ReconfigVM_Task', + vm_moref, + spec=new_spec) + try: + self._session.wait_for_task(task) + LOG.info(_LI("Updated VM %(moref)s spec - detached an interface"), + {'moref': vm_moref.value}) + except Exception as e: + LOG.error(_LE("Failed to reconfigure vm moref %(moref)s: %(e)s"), + {'moref': vm_moref.value, 'e': e}) + + def get_vm_interfaces_info(self, vm_moref): + hardware_devices = self._session.invoke_api(vim_util, + "get_object_property", + self._session.vim, + vm_moref, + "config.hardware.device") + return hardware_devices diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/ports.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/ports.py index ace44d281b..7f888cb903 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/ports.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/ports.py @@ -17,10 +17,11 @@ import logging from sqlalchemy.orm import exc -from vmware_nsx._i18n import _LI, _LW +from vmware_nsx._i18n import _LE, _LI, _LW from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.db import db as nsx_db from vmware_nsx.db import nsx_models +from vmware_nsx.dvs import dvs from vmware_nsx.nsxlib.v3 import resources from vmware_nsx.plugins.nsx_v3 import plugin from vmware_nsx.services.qos.common import utils as qos_utils @@ -58,6 +59,20 @@ def get_port_nsx_id(session, neutron_id): pass +def get_network_nsx_id(session, neutron_id): + # get the nsx switch id from the DB mapping + mappings = nsx_db.get_nsx_switch_ids(session, neutron_id) + if not mappings or len(mappings) == 0: + LOG.debug("Unable to find NSX mappings for neutron " + "network %s.", neutron_id) + # fallback in case we didn't find the id in the db mapping + # This should not happen, but added here in case the network was + # created before this code was added. + return neutron_id + else: + return mappings[0] + + def get_port_and_profile_clients(): _nsx_client = v3_utils.get_nsxv3_client() return (resources.LogicalPort(_nsx_client), @@ -168,6 +183,83 @@ def list_missing_ports(resource, event, trigger, **kwargs): LOG.info(_LI("All internal ports verified on the NSX manager")) +def get_vm_network_device(dvs_mng, vm_moref, mac_address): + """Return the network device with MAC 'mac_address'. + + This code was inspired by Nova vif.get_network_device + """ + hardware_devices = dvs_mng.get_vm_interfaces_info(vm_moref) + if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice": + hardware_devices = hardware_devices.VirtualDevice + for device in hardware_devices: + if hasattr(device, 'macAddress'): + if device.macAddress == mac_address: + return device + + +def migrate_compute_ports_vms(resource, event, trigger, **kwargs): + """Update the VMs ports on the backend after migrating nsx-v -> nsx-v3 + + After using api_replay to migrate the neutron data from NSX-V to NSX-T + we need to update the VM ports to use OpaqueNetwork instead of + DistributedVirtualPortgroup + """ + # Connect to the DVS manager, using the configuration parameters + try: + dvs_mng = dvs.DvsManager() + except Exception as e: + LOG.error(_LE("Cannot connect to the DVS: Please update the [dvs] " + "section in the nsx.ini file: %s"), e) + return + + # Go over all the compute ports from the plugin + plugin = PortsPlugin() + admin_cxt = neutron_context.get_admin_context() + port_filters = {'device_owner': ['compute:None']} + neutron_ports = plugin.get_ports(admin_cxt, filters=port_filters) + + for port in neutron_ports: + device_id = port.get('device_id') + + # get the vm moref & spec from the DVS + vm_moref = dvs_mng.get_vm_moref_obj(device_id) + vm_spec = dvs_mng.get_vm_spec(vm_moref) + + # Go over the VM interfaces and check if it should be updated + update_spec = False + for prop in vm_spec.propSet: + if (prop.name == 'network' and + hasattr(prop.val, 'ManagedObjectReference')): + for net in prop.val.ManagedObjectReference: + if net._type == 'DistributedVirtualPortgroup': + update_spec = True + + if not update_spec: + LOG.info(_LI("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(dvs_mng, vm_moref, port['mac_address']) + if device is None: + LOG.warning(_LW("No device with MAC address %s exists on the VM"), + port['mac_address']) + continue + device_type = device.__class__.__name__ + + LOG.info(_LI("Detaching old interface from VM %s"), device_id) + dvs_mng.detach_vm_interface(vm_moref, device) + + # add the new interface as OpaqueNetwork + LOG.info(_LI("Attaching new interface to VM %s"), device_id) + nsx_net_id = get_network_nsx_id(admin_cxt.session, port['network_id']) + dvs_mng.attach_vm_interface(vm_moref, port['id'], port['mac_address'], + nsx_net_id, device_type) + + registry.subscribe(list_missing_ports, constants.PORTS, shell.Operations.LIST_MISMATCHES.value) + +registry.subscribe(migrate_compute_ports_vms, + constants.PORTS, + shell.Operations.NSX_MIGRATE_V_V3.value) diff --git a/vmware_nsx/shell/resources.py b/vmware_nsx/shell/resources.py index ca0ae99d38..a249f36137 100644 --- a/vmware_nsx/shell/resources.py +++ b/vmware_nsx/shell/resources.py @@ -46,6 +46,7 @@ class Operations(enum.Enum): NSX_UPDATE_SECRET = 'nsx-update-secret' NSX_RECREATE = 'nsx-recreate' MIGRATE_TO_DYNAMIC_CRITERIA = 'migrate-to-dynamic-criteria' + NSX_MIGRATE_V_V3 = 'nsx-migrate-v-v3' STATUS = 'status' ops = [op.value for op in Operations] @@ -73,7 +74,8 @@ nsxv3_resources = { constants.NETWORKS: Resource(constants.NETWORKS, [Operations.LIST_MISMATCHES.value]), constants.PORTS: Resource(constants.PORTS, - [Operations.LIST_MISMATCHES.value]), + [Operations.LIST_MISMATCHES.value, + Operations.NSX_MIGRATE_V_V3.value]), constants.ROUTERS: Resource(constants.ROUTERS, [Operations.LIST_MISMATCHES.value]), constants.DHCP_BINDING: Resource(constants.DHCP_BINDING,