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(