[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
- Update the VMs ports on the backend after migrating nsx-v -> nsx-v3
nsxadmin -r ports -o nsx-migrate-v-v3
Security Groups
~~~~~~~~~~~~~~~

View File

@ -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

View File

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

View File

@ -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,