From 8ee49c6aedec4203fc7160b440923a86a983cd27 Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Thu, 19 Mar 2015 02:34:22 -0700 Subject: [PATCH] VMware: expand support for Opaque networks The original support for Opaque networks make use of a configuration flag indicating that a global integration bridge was configured on the ESX host. If this global integration bridge was used then that would be selected as the opaque network. The next generation NSX neutron plugin will not make use of the integration bridge. This will require that we make use of the network ID for the opaque network. The support for VC 6.0 has specific support for this. For 5.5 we make use of a DynamicProperty setting to achieve the same goal. DocImpact For backward compatible opaque support the setting 'CONF.vmware.integration_bridge' needs to be set. If this is not set then the network id will be used as the opaque id. In addition to this the default value has been set to 'None'. Implements blueprint vmware-expand-opaque-support Change-Id: I309ca2bc8186b8eefc0a979d039dd4cd2a6f89f6 --- nova/tests/unit/virt/vmwareapi/test_vif.py | 223 ++++++++---------- .../tests/unit/virt/vmwareapi/test_vm_util.py | 54 ++++- nova/virt/vmwareapi/constants.py | 2 + nova/virt/vmwareapi/vif.py | 98 ++++---- nova/virt/vmwareapi/vm_util.py | 10 + 5 files changed, 199 insertions(+), 188 deletions(-) diff --git a/nova/tests/unit/virt/vmwareapi/test_vif.py b/nova/tests/unit/virt/vmwareapi/test_vif.py index d48c95f00c79..81fce0db7e37 100644 --- a/nova/tests/unit/virt/vmwareapi/test_vif.py +++ b/nova/tests/unit/virt/vmwareapi/test_vif.py @@ -16,7 +16,7 @@ import mock from oslo_config import cfg from oslo_vmware import exceptions as vexc -from oslo_vmware import vim_util as vutil +from oslo_vmware import vim_util from nova import exception from nova.network import model as network_model @@ -24,6 +24,7 @@ from nova import test from nova.tests.unit import matchers from nova.tests.unit import utils from nova.tests.unit.virt.vmwareapi import fake +from nova.virt.vmwareapi import constants from nova.virt.vmwareapi import network_util from nova.virt.vmwareapi import vif from nova.virt.vmwareapi import vm_util @@ -41,7 +42,7 @@ class VMwareVifTestCase(test.NoDBTestCase): vlan=3, bridge_interface='eth0', injected=True) - + self._network = network self.vif = network_model.NetworkInfo([ network_model.VIF(id=None, address='DE:AD:BE:EF:00:00', @@ -132,12 +133,6 @@ class VMwareVifTestCase(test.NoDBTestCase): create_vlan=False) self.assertThat(ref, matchers.DictMatches(network_ref)) - def test_get_network_ref_neutron(self): - self.mox.StubOutWithMock(vif, 'get_neutron_network') - vif.get_neutron_network(self.session, 'fa0', self.cluster, self.vif) - self.mox.ReplayAll() - vif.get_network_ref(self.session, self.cluster, self.vif, True) - def test_get_network_ref_flat_dhcp(self): self.mox.StubOutWithMock(vif, 'ensure_vlan_bridge') vif.ensure_vlan_bridge(self.session, self.vif, cluster=self.cluster, @@ -168,110 +163,6 @@ class VMwareVifTestCase(test.NoDBTestCase): ])[0] vif.get_network_ref(self.session, self.cluster, self.vif, False) - def test_get_network_ref_bridge_from_opaque(self): - opaque_networks = [{'opaqueNetworkId': 'bridge_id', - 'opaqueNetworkName': 'name', - 'opaqueNetworkType': 'OpaqueNetwork'}] - network_ref = vif._get_network_ref_from_opaque(opaque_networks, - 'integration_bridge', 'bridge_id') - self.assertEqual('bridge_id', network_ref['network-id']) - - def test_get_network_ref_multiple_bridges_from_opaque(self): - opaque_networks = [{'opaqueNetworkId': 'bridge_id1', - 'opaqueNetworkName': 'name1', - 'opaqueNetworkType': 'OpaqueNetwork'}, - {'opaqueNetworkId': 'bridge_id2', - 'opaqueNetworkName': 'name2', - 'opaqueNetworkType': 'OpaqueNetwork'}] - network_ref = vif._get_network_ref_from_opaque(opaque_networks, - 'integration_bridge', 'bridge_id2') - self.assertEqual('bridge_id2', network_ref['network-id']) - - def test_get_network_ref_integration(self): - opaque_networks = [{'opaqueNetworkId': 'integration_bridge', - 'opaqueNetworkName': 'name', - 'opaqueNetworkType': 'OpaqueNetwork'}] - network_ref = vif._get_network_ref_from_opaque(opaque_networks, - 'integration_bridge', 'bridge_id') - self.assertEqual('integration_bridge', network_ref['network-id']) - - def test_get_network_ref_bridge_none(self): - opaque_networks = [{'opaqueNetworkId': 'bridge_id1', - 'opaqueNetworkName': 'name1', - 'opaqueNetworkType': 'OpaqueNetwork'}, - {'opaqueNetworkId': 'bridge_id2', - 'opaqueNetworkName': 'name2', - 'opaqueNetworkType': 'OpaqueNetwork'}] - network_ref = vif._get_network_ref_from_opaque(opaque_networks, - 'integration_bridge', 'bridge_id') - self.assertIsNone(network_ref) - - def test_get_network_ref_integration_multiple(self): - opaque_networks = [{'opaqueNetworkId': 'bridge_id1', - 'opaqueNetworkName': 'name1', - 'opaqueNetworkType': 'OpaqueNetwork'}, - {'opaqueNetworkId': 'integration_bridge', - 'opaqueNetworkName': 'name2', - 'opaqueNetworkType': 'OpaqueNetwork'}] - network_ref = vif._get_network_ref_from_opaque(opaque_networks, - 'integration_bridge', 'bridge_id') - self.assertIsNone(network_ref) - - def test_get_neutron_network(self): - self.mox.StubOutWithMock(vm_util, 'get_host_ref') - self.mox.StubOutWithMock(self.session, '_call_method') - self.mox.StubOutWithMock(vif, '_get_network_ref_from_opaque') - vm_util.get_host_ref(self.session, - self.cluster).AndReturn('fake-host') - opaque = fake.DataObject() - opaque.HostOpaqueNetworkInfo = ['fake-network-info'] - self.session._call_method(vutil, "get_object_property", - 'fake-host', 'config.network.opaqueNetwork').AndReturn(opaque) - vif._get_network_ref_from_opaque(opaque.HostOpaqueNetworkInfo, - CONF.vmware.integration_bridge, - self.vif['network']['id']).AndReturn('fake-network-ref') - self.mox.ReplayAll() - network_ref = vif.get_neutron_network(self.session, - self.vif['network']['id'], - self.cluster, - self.vif) - self.assertEqual(network_ref, 'fake-network-ref') - - def test_get_neutron_network_opaque_network_not_found(self): - self.mox.StubOutWithMock(vm_util, 'get_host_ref') - self.mox.StubOutWithMock(self.session, '_call_method') - self.mox.StubOutWithMock(vif, '_get_network_ref_from_opaque') - vm_util.get_host_ref(self.session, - self.cluster).AndReturn('fake-host') - opaque = fake.DataObject() - opaque.HostOpaqueNetworkInfo = ['fake-network-info'] - self.session._call_method(vutil, "get_object_property", - 'fake-host', 'config.network.opaqueNetwork').AndReturn(opaque) - vif._get_network_ref_from_opaque(opaque.HostOpaqueNetworkInfo, - CONF.vmware.integration_bridge, - self.vif['network']['id']).AndReturn(None) - self.mox.ReplayAll() - self.assertRaises(exception.NetworkNotFoundForBridge, - vif.get_neutron_network, self.session, - self.vif['network']['id'], self.cluster, self.vif) - - def test_get_neutron_network_bridge_network_not_found(self): - self.mox.StubOutWithMock(vm_util, 'get_host_ref') - self.mox.StubOutWithMock(self.session, '_call_method') - self.mox.StubOutWithMock(network_util, 'get_network_with_the_name') - vm_util.get_host_ref(self.session, - self.cluster).AndReturn('fake-host') - opaque = fake.DataObject() - opaque.HostOpaqueNetworkInfo = ['fake-network-info'] - self.session._call_method(vutil, "get_object_property", - 'fake-host', 'config.network.opaqueNetwork').AndReturn(None) - network_util.get_network_with_the_name(self.session, 0, - self.cluster).AndReturn(None) - self.mox.ReplayAll() - self.assertRaises(exception.NetworkNotFoundForBridge, - vif.get_neutron_network, self.session, - self.vif['network']['id'], self.cluster, self.vif) - def test_create_port_group_already_exists(self): def fake_call_method(module, method, *args, **kwargs): if method == 'AddPortGroup': @@ -304,20 +195,6 @@ class VMwareVifTestCase(test.NoDBTestCase): 'vswitch_name', vlan_id=0, cluster=None) - def test_get_neutron_network_invalid_property(self): - def fake_call_method(module, method, *args, **kwargs): - if method == 'get_object_property': - raise vexc.InvalidPropertyException() - - with test.nested( - mock.patch.object(vm_util, 'get_host_ref'), - mock.patch.object(self.session, '_call_method', - fake_call_method), - mock.patch.object(network_util, 'get_network_with_the_name') - ) as (_get_host, _call_method, _get_name): - vif.get_neutron_network(self.session, 'network_name', - 'cluster', self.vif) - def test_get_vif_info_none(self): vif_info = vif.get_vif_info('fake_session', 'fake_cluster', 'is_neutron', 'fake_model', None) @@ -339,3 +216,97 @@ class VMwareVifTestCase(test.NoDBTestCase): 'network_ref': 'fake_ref', 'vif_model': 'fake_model'}] self.assertEqual(expected, vif_info) + + @mock.patch.object(vif, '_check_ovs_supported_version') + def test_get_neutron_network_ovs_integration_bridge(self, + mock_check): + self.flags(integration_bridge='fake-bridge-id', group='vmware') + vif_info = network_model.NetworkInfo([ + network_model.VIF(type=network_model.VIF_TYPE_OVS, + address='DE:AD:BE:EF:00:00', + network=self._network)] + )[0] + network_ref = vif._get_neutron_network('fake-session', + 'fake-cluster', + vif_info) + expected_ref = {'type': 'OpaqueNetwork', + 'network-id': 'fake-bridge-id', + 'network-type': 'opaque', + 'use-external-id': False} + self.assertEqual(expected_ref, network_ref) + mock_check.assert_called_once_with('fake-session') + + @mock.patch.object(vif, '_check_ovs_supported_version') + def test_get_neutron_network_ovs(self, mock_check): + vif_info = network_model.NetworkInfo([ + network_model.VIF(type=network_model.VIF_TYPE_OVS, + address='DE:AD:BE:EF:00:00', + network=self._network)] + )[0] + network_ref = vif._get_neutron_network('fake-session', + 'fake-cluster', + vif_info) + expected_ref = {'type': 'OpaqueNetwork', + 'network-id': 0, + 'network-type': 'nsx.LogicalSwitch', + 'use-external-id': True} + self.assertEqual(expected_ref, network_ref) + mock_check.assert_called_once_with('fake-session') + + @mock.patch.object(network_util, 'get_network_with_the_name') + def test_get_neutron_network_dvs(self, mock_network_name): + fake_network_obj = {'type': 'DistributedVirtualPortgroup', + 'dvpg': 'fake-key', + 'dvsw': 'fake-props'} + mock_network_name.return_value = fake_network_obj + vif_info = network_model.NetworkInfo([ + network_model.VIF(type=network_model.VIF_TYPE_DVS, + address='DE:AD:BE:EF:00:00', + network=self._network)] + )[0] + network_ref = vif._get_neutron_network('fake-session', + 'fake-cluster', + vif_info) + mock_network_name.assert_called_once_with('fake-session', + 'fa0', + 'fake-cluster') + self.assertEqual(fake_network_obj, network_ref) + + @mock.patch.object(network_util, 'get_network_with_the_name', + return_value=None) + def test_get_neutron_network_dvs_no_match(self, mock_network_name): + vif_info = network_model.NetworkInfo([ + network_model.VIF(type=network_model.VIF_TYPE_DVS, + address='DE:AD:BE:EF:00:00', + network=self._network)] + )[0] + self.assertRaises(exception.NetworkNotFoundForBridge, + vif._get_neutron_network, + 'fake-session', + 'fake-cluster', + vif_info) + + def test_get_neutron_network_invalid_type(self): + vif_info = network_model.NetworkInfo([ + network_model.VIF(address='DE:AD:BE:EF:00:00', + network=self._network)] + )[0] + self.assertRaises(exception.InvalidInput, + vif._get_neutron_network, + 'fake-session', + 'fake-cluster', + vif_info) + + @mock.patch.object(vif.LOG, 'warning') + @mock.patch.object(vim_util, 'get_vc_version', + return_value='5.0.0') + def test_check_invalid_ovs_version(self, mock_version, mock_warning): + vif._check_ovs_supported_version('fake_session') + # assert that the min version is in a warning message + expected_arg = {'version': constants.MIN_VC_OVS_VERSION} + version_arg_found = False + for call in mock_warning.call_args_list: + if call[0][1] == expected_arg: + version_arg_found = True + break + self.assertTrue(version_arg_found) diff --git a/nova/tests/unit/virt/vmwareapi/test_vm_util.py b/nova/tests/unit/virt/vmwareapi/test_vm_util.py index 72f78c334946..b184bcce9315 100644 --- a/nova/tests/unit/virt/vmwareapi/test_vm_util.py +++ b/nova/tests/unit/virt/vmwareapi/test_vm_util.py @@ -1012,14 +1012,13 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): _wait_for_task.assert_called_once_with( 'fake_reconfigure_task') - def test_get_network_attach_config_spec_opaque(self): - vif_info = {'network_name': 'br-int', - 'mac_address': '00:00:00:ca:fe:01', - 'network_ref': {'type': 'OpaqueNetwork', - 'network-id': 'fake-network-id', - 'network-type': 'opaque'}, - 'iface_id': 7, - 'vif_model': 'VirtualE1000'} + def _get_network_attach_config_spec_opaque(self, network_ref, + vc6_onwards=False): + vif_info = {'network_name': 'fake-name', + 'mac_address': '00:00:00:ca:fe:01', + 'network_ref': network_ref, + 'iface_id': 7, + 'vif_model': 'VirtualE1000'} fake_factory = fake.FakeFactory() result = vm_util.get_network_attach_config_spec( fake_factory, vif_info, 1) @@ -1038,6 +1037,14 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): device = fake_factory.create('ns0:VirtualE1000') device.macAddress = vif_info['mac_address'] + if network_ref['use-external-id']: + if vc6_onwards: + device.externalId = vif_info['iface_id'] + else: + dp = fake_factory.create('ns0:DynamicProperty') + dp.name = '__externalId__' + dp.val = vif_info['iface_id'] + device.dynamicProperty = [dp] device.addressType = 'manual' connectable = fake_factory.create('ns0:VirtualDeviceConnectInfo') connectable.allowGuestControl = True @@ -1055,6 +1062,37 @@ class VMwareVMUtilTestCase(test.NoDBTestCase): self.assertEqual(expected, result) + def test_get_network_attach_config_spec_opaque_integration_bridge(self): + network_ref = {'type': 'OpaqueNetwork', + 'network-id': 'fake-network-id', + 'network-type': 'opaque', + 'use-external-id': False} + self._get_network_attach_config_spec_opaque(network_ref) + + def test_get_network_attach_config_spec_opaque(self): + network_ref = {'type': 'OpaqueNetwork', + 'network-id': 'fake-network-id', + 'network-type': 'nsx.LogicalSwitch', + 'use-external-id': True} + self._get_network_attach_config_spec_opaque(network_ref) + + @mock.patch.object(fake, 'DataObject') + def test_get_network_attach_config_spec_opaque_vc6_onwards(self, + mock_object): + # Add new attribute externalId supported from VC6 + class FakeVirtualE1000(fake.DataObject): + def __init__(self): + super(FakeVirtualE1000, self).__init__() + self.externalId = None + + mock_object.return_value = FakeVirtualE1000 + network_ref = {'type': 'OpaqueNetwork', + 'network-id': 'fake-network-id', + 'network-type': 'nsx.LogicalSwitch', + 'use-external-id': True} + self._get_network_attach_config_spec_opaque(network_ref, + vc6_onwards=True) + def test_get_network_attach_config_spec_dvs(self): vif_info = {'network_name': 'br100', 'mac_address': '00:00:00:ca:fe:01', diff --git a/nova/virt/vmwareapi/constants.py b/nova/virt/vmwareapi/constants.py index 7842b17d9e78..0abd126e338a 100644 --- a/nova/virt/vmwareapi/constants.py +++ b/nova/virt/vmwareapi/constants.py @@ -19,6 +19,8 @@ Shared constants across the VMware driver from nova.network import model as network_model MIN_VC_VERSION = '5.1.0' +# The minimum VC version for Neutron 'ovs' port type support +MIN_VC_OVS_VERSION = '5.5.0' DISK_FORMAT_ISO = 'iso' DISK_FORMAT_VMDK = 'vmdk' diff --git a/nova/virt/vmwareapi/vif.py b/nova/virt/vmwareapi/vif.py index 660e641dea25..ccd53763c092 100644 --- a/nova/virt/vmwareapi/vif.py +++ b/nova/virt/vmwareapi/vif.py @@ -17,12 +17,13 @@ from oslo_config import cfg from oslo_log import log as logging -from oslo_vmware import exceptions as vexc -from oslo_vmware import vim_util as vutil +from oslo_vmware import vim_util from nova import exception -from nova.i18n import _LW +from nova.i18n import _, _LW from nova.network import model +from nova import utils +from nova.virt.vmwareapi import constants from nova.virt.vmwareapi import network_util from nova.virt.vmwareapi import vm_util @@ -34,8 +35,11 @@ vmwareapi_vif_opts = [ default='vmnic0', help='Physical ethernet adapter name for vlan networking'), cfg.StrOpt('integration_bridge', - default='br-int', - help='Name of Integration Bridge'), + help='This option should be configured only when using the ' + 'NSX-MH Neutron plugin. This is the name of the ' + 'integration bridge on the ESXi. This should not be set ' + 'for any other Neutron plugin. Hence the default value ' + 'is not set.'), ] CONF.register_opts(vmwareapi_vif_opts, 'vmware') @@ -99,63 +103,49 @@ def ensure_vlan_bridge(session, vif, cluster=None, create_vlan=True): return network_ref -def _is_valid_opaque_network_id(opaque_id, bridge_id, integration_bridge, - num_networks): - return (opaque_id == bridge_id or - (num_networks == 1 and - opaque_id == integration_bridge)) +def _check_ovs_supported_version(session): + # The port type 'ovs' is only support by the VC version 5.5 onwards + min_version = utils.convert_version_to_int( + constants.MIN_VC_OVS_VERSION) + vc_version = utils.convert_version_to_int( + vim_util.get_vc_version(session)) + if vc_version < min_version: + LOG.warning(_LW('VMware vCenter version less than %(version)s ' + 'does not support the \'ovs\' port type.'), + {'version': constants.MIN_VC_OVS_VERSION}) -def _get_network_ref_from_opaque(opaque_networks, integration_bridge, bridge): - num_networks = len(opaque_networks) - for network in opaque_networks: - if _is_valid_opaque_network_id(network['opaqueNetworkId'], bridge, - integration_bridge, num_networks): - return {'type': 'OpaqueNetwork', - 'network-id': network['opaqueNetworkId'], - 'network-name': network['opaqueNetworkName'], - 'network-type': network['opaqueNetworkType']} - LOG.warning(_LW("No valid network found in %(opaque)s, from %(bridge)s " - "or %(integration_bridge)s"), - {'opaque': opaque_networks, 'bridge': bridge, - 'integration_bridge': integration_bridge}) - - -def _get_opaque_network(session, cluster): - host = vm_util.get_host_ref(session, cluster) - try: - opaque = session._call_method(vutil, - "get_object_property", - host, - "config.network.opaqueNetwork") - except vexc.InvalidPropertyException: - opaque = None - return opaque - - -def get_neutron_network(session, network_name, cluster, vif): - opaque = None - if vif['type'] != model.VIF_TYPE_DVS: - opaque = _get_opaque_network(session, cluster) - if opaque: - bridge = vif['network']['id'] - opaque_networks = opaque.HostOpaqueNetworkInfo - network_ref = _get_network_ref_from_opaque(opaque_networks, - CONF.vmware.integration_bridge, bridge) - else: - bridge = network_name +def _get_neutron_network(session, cluster, vif): + if vif['type'] == model.VIF_TYPE_OVS: + _check_ovs_supported_version(session) + # Check if this is the NSX-MH plugin is used + if CONF.vmware.integration_bridge: + net_id = CONF.vmware.integration_bridge + use_external_id = False + network_type = 'opaque' + else: + net_id = vif['network']['id'] + use_external_id = True + network_type = 'nsx.LogicalSwitch' + network_ref = {'type': 'OpaqueNetwork', + 'network-id': net_id, + 'network-type': network_type, + 'use-external-id': use_external_id} + elif vif['type'] == model.VIF_TYPE_DVS: + network_id = vif['network']['bridge'] network_ref = network_util.get_network_with_the_name( - session, network_name, cluster) - if not network_ref: - raise exception.NetworkNotFoundForBridge(bridge=bridge) + session, network_id, cluster) + if not network_ref: + raise exception.NetworkNotFoundForBridge(bridge=network_id) + else: + reason = _('vif type %s not supported') % vif['type'] + raise exception.InvalidInput(reason=reason) return network_ref def get_network_ref(session, cluster, vif, is_neutron): if is_neutron: - network_name = (vif['network']['bridge'] or - CONF.vmware.integration_bridge) - network_ref = get_neutron_network(session, network_name, cluster, vif) + network_ref = _get_neutron_network(session, cluster, vif) else: create_vlan = vif['network'].get_meta('should_create_vlan', False) network_ref = ensure_vlan_bridge(session, vif, cluster=cluster, diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py index 5e19cb0a974b..b3758e85f0dd 100644 --- a/nova/virt/vmwareapi/vm_util.py +++ b/nova/virt/vmwareapi/vm_util.py @@ -428,6 +428,16 @@ def _create_vif_spec(client_factory, vif_info): 'ns0:VirtualEthernetCardOpaqueNetworkBackingInfo') backing.opaqueNetworkId = network_ref['network-id'] backing.opaqueNetworkType = network_ref['network-type'] + # Configure externalId + if network_ref['use-external-id']: + # externalId is only supported from vCenter 6.0 onwards + if hasattr(net_device, 'externalId'): + net_device.externalId = vif_info['iface_id'] + else: + dp = client_factory.create('ns0:DynamicProperty') + dp.name = "__externalId__" + dp.val = vif_info['iface_id'] + net_device.dynamicProperty = [dp] elif (network_ref and network_ref['type'] == "DistributedVirtualPortgroup"): backing = client_factory.create(