[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
This commit is contained in:
Adit Sarfaty 2016-08-24 08:38:24 +03:00
parent c3b287d4b8
commit 353be39d92
4 changed files with 211 additions and 12 deletions

View File

@ -174,6 +174,10 @@ Ports
nsxadmin -r ports -o list-mismatches 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 Security Groups
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~

View File

@ -308,7 +308,17 @@ class DvsManager(object):
{'net_id': net_id, {'net_id': net_id,
'dvs': self._dvs_moref.value}) '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. """Get reference to the VM.
The method will make use of FindAllByUuid to get the VM reference. The method will make use of FindAllByUuid to get the VM reference.
This method finds all VM's on the backend that match the This method finds all VM's on the backend that match the
@ -323,14 +333,105 @@ class DvsManager(object):
vmSearch=True, vmSearch=True,
instanceUuid=True) instanceUuid=True)
if vm_refs: if vm_refs:
return vm_refs[0].value return vm_refs[0]
def get_portgroup_info(self, pg_moref): def get_vm_moref(self, instance_uuid):
"""Get portgroup information.""" """Get reference to the VM.
# Expand the properties to collect on need basis. """
properties = ['name'] vm_ref = self.get_vm_moref_obj(instance_uuid)
pg_info = self._session.invoke_api(vim_util, if vm_ref:
'get_object_properties_dict', 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, self._session.vim,
pg_moref, properties) vm_moref, ['network'])[0]
return pg_info 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

View File

@ -17,10 +17,11 @@ import logging
from sqlalchemy.orm import exc 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.common import exceptions as nsx_exc
from vmware_nsx.db import db as nsx_db from vmware_nsx.db import db as nsx_db
from vmware_nsx.db import nsx_models from vmware_nsx.db import nsx_models
from vmware_nsx.dvs import dvs
from vmware_nsx.nsxlib.v3 import resources from vmware_nsx.nsxlib.v3 import resources
from vmware_nsx.plugins.nsx_v3 import plugin from vmware_nsx.plugins.nsx_v3 import plugin
from vmware_nsx.services.qos.common import utils as qos_utils from vmware_nsx.services.qos.common import utils as qos_utils
@ -58,6 +59,20 @@ def get_port_nsx_id(session, neutron_id):
pass 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(): def get_port_and_profile_clients():
_nsx_client = v3_utils.get_nsxv3_client() _nsx_client = v3_utils.get_nsxv3_client()
return (resources.LogicalPort(_nsx_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")) 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, registry.subscribe(list_missing_ports,
constants.PORTS, constants.PORTS,
shell.Operations.LIST_MISMATCHES.value) shell.Operations.LIST_MISMATCHES.value)
registry.subscribe(migrate_compute_ports_vms,
constants.PORTS,
shell.Operations.NSX_MIGRATE_V_V3.value)

View File

@ -46,6 +46,7 @@ class Operations(enum.Enum):
NSX_UPDATE_SECRET = 'nsx-update-secret' NSX_UPDATE_SECRET = 'nsx-update-secret'
NSX_RECREATE = 'nsx-recreate' NSX_RECREATE = 'nsx-recreate'
MIGRATE_TO_DYNAMIC_CRITERIA = 'migrate-to-dynamic-criteria' MIGRATE_TO_DYNAMIC_CRITERIA = 'migrate-to-dynamic-criteria'
NSX_MIGRATE_V_V3 = 'nsx-migrate-v-v3'
STATUS = 'status' STATUS = 'status'
ops = [op.value for op in Operations] ops = [op.value for op in Operations]
@ -73,7 +74,8 @@ nsxv3_resources = {
constants.NETWORKS: Resource(constants.NETWORKS, constants.NETWORKS: Resource(constants.NETWORKS,
[Operations.LIST_MISMATCHES.value]), [Operations.LIST_MISMATCHES.value]),
constants.PORTS: Resource(constants.PORTS, constants.PORTS: Resource(constants.PORTS,
[Operations.LIST_MISMATCHES.value]), [Operations.LIST_MISMATCHES.value,
Operations.NSX_MIGRATE_V_V3.value]),
constants.ROUTERS: Resource(constants.ROUTERS, constants.ROUTERS: Resource(constants.ROUTERS,
[Operations.LIST_MISMATCHES.value]), [Operations.LIST_MISMATCHES.value]),
constants.DHCP_BINDING: Resource(constants.DHCP_BINDING, constants.DHCP_BINDING: Resource(constants.DHCP_BINDING,