Skip to content

Commit 23a4b27

Browse files
stephenfinauniyal61
authored andcommitted
libvirt: Delegate OVS plug to os-vif
os-vif 1.15.0 added the ability to create an OVS port during plugging by specifying the 'create_port' attribute in the 'port_profile' field. By delegating port creation to os-vif, we can rely on it's 'isolate_vif' config option [1] that will temporarily configure the VLAN to 4095 (0xfff), which is reserved for implementation use [2] and is used by neutron to as a dead VLAN [3]. By doing this, we ensure VIFs are plugged securely, preventing guests from accessing other tenants' networks before the neutron OVS agent can wire up the port. This change requires a little dance as part of the live migration flow. Since we can't be certain the destination host has a version of os-vif that supports this feature, we need to use a sentinel to indicate when it does. Typically we would do so with a field in 'LibvirtLiveMigrateData', such as the 'src_supports_numa_live_migration' and 'dst_supports_numa_live_migration' fields used to indicate support for NUMA-aware live migration. However, doing this prevents us backporting this important fix since o.vo changes are not backportable. Instead, we (somewhat evilly) rely on the free-form nature of the 'VIFMigrateData.profile_json' string field, which stores JSON blobs and is included in 'LibvirtLiveMigrateData' via the 'vifs' attribute, to transport this sentinel. This is a hack but is necessary to work around the lack of a free-form "capabilities" style dict that would allow us do backportable fixes to live migration features. Note that this change has the knock on effect of modifying the XML generated for OVS ports: when hybrid plug is false will now be of type 'ethernet' rather than 'bridge' as before. This explains the larger than expected test damage but should not affect users. Changes: lower-constraints.txt requirements.txt nova/network/os_vif_util.py nova/tests/unit/virt/libvirt/test_vif.py nova/tests/unit/virt/libvirt/test_driver.py nova/virt/libvirt/driver.py NOTE(stephenfin): Change I362deb1088c88cdcd8219922da9dc9a01b10a940 ("objects: Fix VIFMigrateData.supports_os_vif_delegation setter") which contains an important fix for the original change, is squashed into this change. In addition, the os-vif version bump we introduced in the original version of this patch is not backportable and as a result, we must introduce two additional checks. Both checks ensure we have a suitable version of os-vif and skip the new code paths if not. The first check is in the libvirt driver's 'check_can_live_migrate_destination' function, which as the name suggests runs on the destination host early in the live migration process. If os-vif is not new enough on the destination, we will report that we cannot support os-vif delegation. The other check is in the '_nova_to_osvif_vif_ovs' helper in 'nova.network.os_vif_util'. This simply ensures we don't try to set an invalid attribute on the os-vif object if the version isn't new enough. Two tests are modified to accommodate these checks with similar version check logic. The lower-constraints job can be relied on to validate behavior on the older version of os-vif. [1] https://opendev.org/openstack/os-vif/src/tag/2.4.0/vif_plug_ovs/ovs.py#L90-L93 [2] https://en.wikipedia.org/wiki/IEEE_802.1Q#Frame_format [3] https://answers.launchpad.net/neutron/+question/231806 NOTE(auniyal): * libvirt: Always delegate OVS plug to os-vif is squashed openstack@fa0fb2f * Updated fakelibvirt.Domain.XMLDesc to add pci address for ovs interface, which is a patial backport of openstack@1ad287b Change-Id: I11fb5d3ada7f27b39c183157ea73c8b72b4e672e Depends-On: Id12486b3127ab4ac8ad9ef2b3641da1b79a25a50 Closes-Bug: #1734320 Closes-Bug: #1815989 (cherry picked from commit a62dd42)
1 parent 797c5fa commit 23a4b27

File tree

16 files changed

+419
-132
lines changed

16 files changed

+419
-132
lines changed

nova/compute/manager.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7997,11 +7997,12 @@ def check_can_live_migrate_destination(self, ctxt, instance,
79977997
'but source is either too old, or is set to an '
79987998
'older upgrade level.', instance=instance)
79997999
if self.network_api.supports_port_binding_extension(ctxt):
8000-
# Create migrate_data vifs
8001-
migrate_data.vifs = \
8002-
migrate_data_obj.\
8003-
VIFMigrateData.create_skeleton_migrate_vifs(
8004-
instance.get_network_info())
8000+
# Create migrate_data vifs if not provided by driver.
8001+
if 'vifs' not in migrate_data:
8002+
migrate_data.vifs = (
8003+
migrate_data_obj.
8004+
VIFMigrateData.create_skeleton_migrate_vifs(
8005+
instance.get_network_info()))
80058006
# Claim PCI devices for VIFs on destination (if needed)
80068007
port_id_to_pci = self._claim_pci_for_instance_vifs(
80078008
ctxt, instance)

nova/network/model.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ def __init__(self, id=None, address=None, network=None, type=None,
384384
details=None, devname=None, ovs_interfaceid=None,
385385
qbh_params=None, qbg_params=None, active=False,
386386
vnic_type=VNIC_TYPE_NORMAL, profile=None,
387-
preserve_on_delete=False, **kwargs):
387+
preserve_on_delete=False, delegate_create=False,
388+
**kwargs):
388389
super(VIF, self).__init__()
389390

390391
self['id'] = id
@@ -401,14 +402,15 @@ def __init__(self, id=None, address=None, network=None, type=None,
401402
self['vnic_type'] = vnic_type
402403
self['profile'] = profile
403404
self['preserve_on_delete'] = preserve_on_delete
405+
self['delegate_create'] = delegate_create
404406

405407
self._set_meta(kwargs)
406408

407409
def __eq__(self, other):
408410
keys = ['id', 'address', 'network', 'vnic_type',
409411
'type', 'profile', 'details', 'devname',
410412
'ovs_interfaceid', 'qbh_params', 'qbg_params',
411-
'active', 'preserve_on_delete']
413+
'active', 'preserve_on_delete', 'delegate_create']
412414
return all(self[k] == other[k] for k in keys)
413415

414416
def __ne__(self, other):

nova/network/neutron.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3066,7 +3066,8 @@ def _build_vif_model(self, context, client, current_neutron_port,
30663066
ovs_interfaceid=ovs_interfaceid,
30673067
devname=devname,
30683068
active=vif_active,
3069-
preserve_on_delete=preserve_on_delete
3069+
preserve_on_delete=preserve_on_delete,
3070+
delegate_create=True,
30703071
)
30713072

30723073
def _build_network_info_model(self, context, instance, networks=None,

nova/network/os_vif_util.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from os_vif import objects
2222
from oslo_config import cfg
2323
from oslo_log import log as logging
24+
import pbr.version
2425

2526
from nova import exception
2627
from nova.i18n import _
@@ -347,6 +348,27 @@ def _nova_to_osvif_vif_ovs(vif):
347348
vif_name=vif_name,
348349
bridge_name=_get_hybrid_bridge_name(vif))
349350
else:
351+
# NOTE(stephenfin): The 'create_port' attribute was added in os-vif
352+
# 1.15.0 and isn't available with the lowest version supported here
353+
if (
354+
pbr.version.VersionInfo('os-vif').semantic_version() >=
355+
pbr.version.SemanticVersion.from_pip_string('1.15.0')
356+
):
357+
profile.create_port = vif.get('delegate_create', False)
358+
elif vif.get('delegate_create', False):
359+
# NOTE(stephenfin): This should never happen, since if the
360+
# 'delegate_create' attribute is true then it was set by the
361+
# destination compute node as part of the live migration operation:
362+
# the same destination compute node that we are currently running
363+
# on. We include it for sanity-preservation
364+
LOG.warning(
365+
'os-vif 1.15.0 or later is required to support '
366+
'delegated port creation but you have %s; consider '
367+
'updating this package to resolve bugs #1734320 and '
368+
'#1815989',
369+
pbr.version.VersionInfo('os-vif').version_string(),
370+
)
371+
350372
obj = _get_vif_instance(
351373
vif,
352374
objects.vif.VIFOpenVSwitch,

nova/objects/migrate_data.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
from nova.objects import base as obj_base
2323
from nova.objects import fields
2424

25-
2625
LOG = log.getLogger(__name__)
26+
OS_VIF_DELEGATION = 'os_vif_delegation'
2727

2828

2929
@obj_base.NovaObjectRegistry.register
@@ -60,6 +60,8 @@ class VIFMigrateData(obj_base.NovaObject):
6060

6161
@property
6262
def vif_details(self):
63+
if 'vif_details_json' not in self:
64+
return {}
6365
return jsonutils.loads(self.vif_details_json)
6466

6567
@vif_details.setter
@@ -68,12 +70,27 @@ def vif_details(self, vif_details_dict):
6870

6971
@property
7072
def profile(self):
73+
if 'profile_json' not in self:
74+
return {}
7175
return jsonutils.loads(self.profile_json)
7276

7377
@profile.setter
7478
def profile(self, profile_dict):
7579
self.profile_json = jsonutils.dumps(profile_dict)
7680

81+
@property
82+
def supports_os_vif_delegation(self):
83+
return self.profile.get(OS_VIF_DELEGATION, False)
84+
85+
# TODO(stephenfin): add a proper delegation field instead of storing this
86+
# info in the profile catch-all blob
87+
@supports_os_vif_delegation.setter
88+
def supports_os_vif_delegation(self, supported):
89+
# we can't simply set the attribute using dict notation since the
90+
# getter returns a copy of the data, not the data itself
91+
self.profile = dict(
92+
self.profile or {}, **{OS_VIF_DELEGATION: supported})
93+
7794
def get_dest_vif(self):
7895
"""Get a destination VIF representation of this object.
7996
@@ -91,6 +108,7 @@ def get_dest_vif(self):
91108
vif['vnic_type'] = self.vnic_type
92109
vif['profile'] = self.profile
93110
vif['details'] = self.vif_details
111+
vif['delegate_create'] = self.supports_os_vif_delegation
94112
return vif
95113

96114
@classmethod

nova/tests/functional/libvirt/test_pci_sriov_servers.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -577,16 +577,15 @@ def test_get_server_diagnostics_server_with_VF(self):
577577
self.start_compute(pci_info=pci_info)
578578

579579
# create the SR-IOV port
580-
self.neutron.create_port({'port': self.neutron.network_4_port_1})
580+
port = self.neutron.create_port(
581+
{'port': self.neutron.network_4_port_1})
581582

582-
# create a server using the VF and multiple networks
583-
extra_spec = {'pci_passthrough:alias': f'{self.VFS_ALIAS_NAME}:1'}
584-
flavor_id = self._create_flavor(extra_spec=extra_spec)
583+
flavor_id = self._create_flavor()
585584
server = self._create_server(
586585
flavor_id=flavor_id,
587586
networks=[
588587
{'uuid': base.LibvirtNeutronFixture.network_1['id']},
589-
{'port': base.LibvirtNeutronFixture.network_4_port_1['id']},
588+
{'port': port['port']['id']},
590589
],
591590
)
592591

@@ -600,13 +599,16 @@ def test_get_server_diagnostics_server_with_VF(self):
600599
base.LibvirtNeutronFixture.network_1_port_2['mac_address'],
601600
diagnostics['nic_details'][0]['mac_address'],
602601
)
603-
self.assertIsNotNone(diagnostics['nic_details'][0]['tx_packets'])
602+
603+
for key in ('rx_packets', 'tx_packets'):
604+
self.assertIn(key, diagnostics['nic_details'][0])
604605

605606
self.assertEqual(
606607
base.LibvirtNeutronFixture.network_4_port_1['mac_address'],
607608
diagnostics['nic_details'][1]['mac_address'],
608609
)
609-
self.assertIsNone(diagnostics['nic_details'][1]['tx_packets'])
610+
for key in ('rx_packets', 'tx_packets'):
611+
self.assertIn(key, diagnostics['nic_details'][1])
610612

611613
def test_create_server_after_change_in_nonsriov_pf_to_sriov_pf(self):
612614
# Starts a compute with PF not configured with SRIOV capabilities

nova/tests/unit/fake_network.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def update_cache_fake(*args, **kwargs):
120120
qbg_params=None,
121121
active=False,
122122
vnic_type='normal',
123-
profile=None,
123+
profile={},
124124
preserve_on_delete=False,
125125
meta={'rxtx_cap': 30},
126126
)

nova/tests/unit/network/test_neutron.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3271,6 +3271,7 @@ def test_build_network_info_model(self, mock_get_client,
32713271
requested_ports[index].get(
32723272
constants.BINDING_PROFILE) or {},
32733273
nw_info.get('profile'))
3274+
self.assertTrue(nw_info.get('delegate_create'))
32743275
index += 1
32753276

32763277
self.assertFalse(nw_infos[0]['active'])

nova/tests/unit/network/test_os_vif_util.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,8 @@ def test_nova_to_osvif_vif_agilio_ovs_fallthrough(self):
458458
subnets=[]),
459459
details={
460460
model.VIF_DETAILS_PORT_FILTER: True,
461-
}
461+
},
462+
delegate_create=False,
462463
)
463464

464465
actual = os_vif_util.nova_to_osvif_vif(vif)
@@ -471,7 +472,8 @@ def test_nova_to_osvif_vif_agilio_ovs_fallthrough(self):
471472
plugin="ovs",
472473
port_profile=osv_objects.vif.VIFPortProfileOpenVSwitch(
473474
interface_id="dc065497-3c8d-4f44-8fb4-e1d33c16a536",
474-
datapath_type=None),
475+
datapath_type=None,
476+
create_port=False),
475477
preserve_on_delete=False,
476478
vif_name="nicdc065497-3c",
477479
network=osv_objects.network.Network(
@@ -588,6 +590,7 @@ def test_nova_to_osvif_vif_ovs_plain(self):
588590
model.VIF_DETAILS_OVS_DATAPATH_TYPE:
589591
model.VIF_DETAILS_OVS_DATAPATH_SYSTEM
590592
},
593+
delegate_create=True,
591594
)
592595

593596
actual = os_vif_util.nova_to_osvif_vif(vif)
@@ -600,7 +603,8 @@ def test_nova_to_osvif_vif_ovs_plain(self):
600603
plugin="ovs",
601604
port_profile=osv_objects.vif.VIFPortProfileOpenVSwitch(
602605
interface_id="dc065497-3c8d-4f44-8fb4-e1d33c16a536",
603-
datapath_type=model.VIF_DETAILS_OVS_DATAPATH_SYSTEM),
606+
datapath_type=model.VIF_DETAILS_OVS_DATAPATH_SYSTEM,
607+
create_port=True),
604608
preserve_on_delete=False,
605609
vif_name="nicdc065497-3c",
606610
network=osv_objects.network.Network(

nova/tests/unit/objects/test_migrate_data.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,24 @@ def test_create_skeleton_migrate_vifs(self):
316316
self.assertEqual(len(vifs), len(mig_vifs))
317317
self.assertEqual([vif['id'] for vif in vifs],
318318
[mig_vif.port_id for mig_vif in mig_vifs])
319+
320+
def test_supports_os_vif_delegation(self):
321+
# first try setting on a object without 'profile' defined
322+
migrate_vif = objects.VIFMigrateData(
323+
port_id=uuids.port_id, vnic_type=network_model.VNIC_TYPE_NORMAL,
324+
vif_type=network_model.VIF_TYPE_OVS, vif_details={},
325+
host='fake-dest-host')
326+
migrate_vif.supports_os_vif_delegation = True
327+
self.assertTrue(migrate_vif.supports_os_vif_delegation)
328+
self.assertEqual(migrate_vif.profile, {'os_vif_delegation': True})
329+
330+
# now do the same but with profile defined
331+
migrate_vif = objects.VIFMigrateData(
332+
port_id=uuids.port_id, vnic_type=network_model.VNIC_TYPE_NORMAL,
333+
vif_type=network_model.VIF_TYPE_OVS, vif_details={},
334+
host='fake-dest-host', profile={'interface_name': 'eth0'})
335+
migrate_vif.supports_os_vif_delegation = True
336+
self.assertTrue(migrate_vif.supports_os_vif_delegation)
337+
self.assertEqual(
338+
migrate_vif.profile,
339+
{'os_vif_delegation': True, 'interface_name': 'eth0'})

0 commit comments

Comments
 (0)