Check mech driver connectivity during port binding

In [1] the concept of "connectivity" was introduced for the ML2 drivers.
This parameter defines the mech driver connectivity type (layer 2, layer
3 only or legacy - not defined).

The spec defined in the blueprint allows to spawn a VM with ports
without IP addresses. As commented in the Nova spec [2], those ports can
be bound only to "l2" drivers.

[1] https://review.opendev.org/#/c/645645/
[2] https://review.opendev.org/#/c/641670/

bp boot-vm-with-unaddressed-port
Related-Bug: #1821058

Change-Id: I438cbab43b45b5f7afc820b77fcf5a0e823d0eff
This commit is contained in:
Rodolfo Alonso Hernandez 2019-08-22 11:07:14 +00:00
parent f5bcca87d1
commit 380825fcf8
5 changed files with 72 additions and 7 deletions

View File

@ -826,10 +826,19 @@ class MechanismManager(stevedore.named.NamedExtensionManager):
'host': context.host})
return False
drivers = self._check_drivers_connectivity(drivers, context)
if not drivers:
LOG.error("Port %(port)s does not have an IP address assigned and "
"there are no driver with 'connectivity' = 'l2'. The "
"port cannot be bound.",
{'port': context.current['id']})
return False
for driver in drivers:
if not self._check_driver_to_bind(driver, segments_to_bind,
context._binding_levels):
continue
try:
context._prepare_to_bind(segments_to_bind)
driver.obj.bind_port(context)
@ -1028,6 +1037,21 @@ class MechanismManager(stevedore.named.NamedExtensionManager):
return False
return True
def _check_drivers_connectivity(self, drivers, port_context):
"""If port does not have an IP address, driver connectivity must be l2
A port without an IP address can be bound only to a mech driver with
"connectivity" = "l2". "legacy" or "l3" (e.g.: Calico) drivers cannot
have a port bound without an IP allocated.
"""
if port_context.current.get('fixed_ips'):
return drivers
return [d for d in drivers if
getattr(d.obj, 'vif_details', {}).get(
portbindings.VIF_DETAILS_CONNECTIVITY) ==
portbindings.CONNECTIVITY_L2]
def get_workers(self):
workers = []
for driver in self.ordered_mech_drivers:

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from oslo_config import cfg
from oslo_config import fixture as config_fixture
@ -59,9 +61,14 @@ class FakePortContext(api.PortContext):
@property
def current(self):
return {'id': PORT_ID,
current_data = {'id': PORT_ID,
portbindings.VNIC_TYPE: self._bound_vnic_type,
portbindings.PROFILE: self._bound_profile}
ret_value = current_data
if self._original:
ret_value = copy.deepcopy(self.original)
ret_value.update(current_data)
return ret_value
@property
def original(self):

View File

@ -45,7 +45,10 @@ class FakeAgentMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
def __init__(self):
sg_enabled = securitygroups_rpc.is_firewall_enabled()
vif_details = {portbindings.CAP_PORT_FILTER: sg_enabled,
portbindings.OVS_HYBRID_PLUG: sg_enabled}
portbindings.OVS_HYBRID_PLUG: sg_enabled,
portbindings.VIF_DETAILS_CONNECTIVITY:
portbindings.CONNECTIVITY_L2,
}
super(FakeAgentMechanismDriver, self).__init__(
# NOTE(yamamoto): l2pop driver has a hardcoded list of
# supported agent types.
@ -64,3 +67,12 @@ class FakeAgentMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
class AnotherFakeAgentMechanismDriver(FakeAgentMechanismDriver):
pass
class FakeAgentMechanismDriverL3(FakeAgentMechanismDriver):
"""ML2 mechanism driver for testing, with L3 connectivity only"""
def __init__(self):
super(FakeAgentMechanismDriverL3, self).__init__()
self.vif_details[portbindings.VIF_DETAILS_CONNECTIVITY] = (
portbindings.CONNECTIVITY_L3)

View File

@ -41,9 +41,10 @@ class TestManagers(base.BaseTestCase):
'network_type': 'vlan',
'physical_network': 'public',
api.SEGMENTATION_ID: 49}]
self.context = FakePortContext(None,
None,
self.segments_to_bind)
original_port = {'fixed_ips': [{'subnet_id': mock.ANY,
'ip_address': mock.ANY}]}
self.context = FakePortContext(None, None, self.segments_to_bind,
original=original_port)
self.context._binding = mock.Mock()
self.context._binding_levels = []
self.context._new_bound_segment = self.segment_id
@ -73,6 +74,26 @@ class TestManagers(base.BaseTestCase):
manager._bind_port_level(self.context, 0, self.segments_to_bind)
self.assertEqual(0, bind_port.call_count)
def _check_drivers_connectivity(self, agents):
cfg.CONF.set_override('mechanism_drivers', agents, group='ml2')
manager = managers.MechanismManager()
return (manager.ordered_mech_drivers,
manager._check_drivers_connectivity(
manager.ordered_mech_drivers, self.context))
def test__check_drivers_connectivity(self):
self.assertEqual(*self._check_drivers_connectivity(['fake_agent']))
def test__check_drivers_connectivity_ip_less_port(self):
self.context._original['fixed_ips'] = []
self.assertEqual(*self._check_drivers_connectivity(['fake_agent']))
def test__check_drivers_connectivity_ip_less_port_l3_only_driver(self):
self.context._original['fixed_ips'] = []
self.assertEqual(
[],
self._check_drivers_connectivity(['fake_agent_l3'])[1])
def test__infer_driver_from_allocation_positive(self):
cfg.CONF.set_override(
'mechanism_drivers', ['fake_agent'], group='ml2')

View File

@ -95,6 +95,7 @@ neutron.ml2.mechanism_drivers =
l2population = neutron.plugins.ml2.drivers.l2pop.mech_driver:L2populationMechanismDriver
sriovnicswitch = neutron.plugins.ml2.drivers.mech_sriov.mech_driver.mech_driver:SriovNicSwitchMechanismDriver
fake_agent = neutron.tests.unit.plugins.ml2.drivers.mech_fake_agent:FakeAgentMechanismDriver
fake_agent_l3 = neutron.tests.unit.plugins.ml2.drivers.mech_fake_agent:FakeAgentMechanismDriverL3
another_fake_agent = neutron.tests.unit.plugins.ml2.drivers.mech_fake_agent:AnotherFakeAgentMechanismDriver
faulty_agent = neutron.tests.unit.plugins.ml2.drivers.mech_faulty_agent:FaultyAgentMechanismDriver
neutron.ml2.extension_drivers =