Skip to content

Commit

Permalink
Allow hostgroups to bind multiple trunks w/ config
Browse files Browse the repository at this point in the history
If a hostgroup has allow_multiple_trunk_ports set it can now have
multiple trunks on it. This allows us to have multiple domains/projects
set VLAN translations for the same device without the networks needing
to be shared in to the same project. As we don't know in this case which
port should dictate the native VLAN we don't set the native VLAN at all
for these groups.
  • Loading branch information
sebageek committed Dec 5, 2023
1 parent 465f733 commit df890d3
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 5 deletions.
10 changes: 10 additions & 0 deletions networking_ccloud/common/config/config_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ class Hostgroup(pydantic.BaseModel):
# vlans that are added to all allowed-vlan list without managing the vlan on switch
extra_vlans: List[pydantic.conint(gt=1, lt=4095)] = None

# allow multiple trunks per hostgroup, not setting the native vlan then (direct bindings only)
# note that this means that no native vlan will be set for ports in this hostgroup, ever
allow_multiple_trunk_ports: bool = False

_vlan_pool: str = None

class Config:
Expand Down Expand Up @@ -348,6 +352,12 @@ def ensure_hostgroups_with_role_are_direct_binding(cls, values):
raise ValueError("Hostgroups for bgws/tranits need to be direct bindings")
return values

@pydantic.root_validator
def ensure_allow_multiple_trunk_ports_are_direct_binding(cls, values):
if values.get('allow_multiple_trunk_ports') and not values.get('direct_binding'):
raise ValueError("allow_multiple_trunk_ports can only be set for direct binding hostgroups")
return values

@pydantic.root_validator
def ensure_members_and_metaflag_match(cls, values):
if 'members' in values:
Expand Down
2 changes: 1 addition & 1 deletion networking_ccloud/ml2/agent/common/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ def add_binding_host_to_config(self, hg_config, network_id, seg_vni, seg_vlan, t
if hg_config.direct_binding and not hg_config.role:
if trunk_vlan:
iface.add_vlan_translation(seg_vlan, trunk_vlan)
else:
elif not hg_config.allow_multiple_trunk_ports:
iface.native_vlan = seg_vlan

def add_vrf_bgp_config(self, switch_names, vrf_name, vrf_networks, vrf_aggregates):
Expand Down
8 changes: 6 additions & 2 deletions networking_ccloud/services/trunk/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,12 @@ def validate_trunk(self, context, trunk, subports):
trunks_on_host = self.fabric_plugin.get_trunks_with_binding_host(context, trunk_host)
trunks = set(trunks_on_host) - set([trunk.id])
if trunks:
raise BadTrunkRequest(trunk_port_id=trunk.port_id,
reason=f"Host {trunk_host} already has trunk {' '.join(trunks)} connected to it")
if hg_config.allow_multiple_trunk_ports:
LOG.info("Creating extra trunk on %s, as allowed by hostgroup (existing trunks: %s)",
trunk_host, ", ".join(trunks_on_host))
else:
raise BadTrunkRequest(trunk_port_id=trunk.port_id,
reason=f"Host {trunk_host} already has trunk {' '.join(trunks)} connected to it")

# subport validation
parent_hg = hg_config.get_parent_metagroup(self.drv_conf)
Expand Down
13 changes: 13 additions & 0 deletions networking_ccloud/tests/unit/common/test_driver_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ def test_hostgroup_require_named_ifaces(self):
config.Hostgroup, binding_hosts=["seagull-compute"],
members=[config.SwitchPort(switch="seagull-sw1")])

def test_hostgroup_multi_trunk_requires_direct_binding(self):
# should work
hg = config.Hostgroup(binding_hosts=["node001-seagull"],
members=[config.SwitchPort(switch="seagull-sw1", name="e1/1/1")],
allow_multiple_trunk_ports=True)
self.assertTrue(hg.allow_multiple_trunk_ports)

# should fail
self.assertRaisesRegex(ValueError, "can only be set for direct binding hostgroups",
config.Hostgroup, binding_hosts=["seagull-compute"],
members=["node001-seagull"], metagroup=True,
allow_multiple_trunk_ports=True)

def test_global_default_vlan_ranges(self):
self.assertRaisesRegex(ValueError, ".*not in format.*", config.GlobalConfig, asn_region=65000,
default_vlan_ranges=["foo:bar"], availability_zones=[])
Expand Down
27 changes: 26 additions & 1 deletion networking_ccloud/tests/unit/ml2/test_mech_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from oslo_config import cfg

from networking_ccloud.common.config import _override_driver_config
from networking_ccloud.common.config import config_driver
from networking_ccloud.common.config import config_oslo # noqa, make sure config opts are there
from networking_ccloud.common import constants as cc_const
from networking_ccloud.common import exceptions as cc_exc
Expand Down Expand Up @@ -114,7 +115,11 @@ def setUp(self):
# squirrel - used for vlan exhaustion test and non-lacp hosts
hg_seagull = cfix.make_metagroup("seagull")
hg_crow = cfix.make_metagroup("crow")
hg_cat = cfix.make_hostgroups("cat")
hg_cat_direct_multitrunk = config_driver.Hostgroup(binding_hosts=["node101-cat"],
members=[config_driver.SwitchPort(switch="cat-sw1",
name="e1/1/1")],
allow_multiple_trunk_ports=True)
hg_cat = cfix.make_hostgroups("cat") + [hg_cat_direct_multitrunk]
hg_squirrel = cfix.make_metagroup("squirrel")

hostgroups = hg_seagull + hg_crow + hg_cat + hg_squirrel
Expand Down Expand Up @@ -214,6 +219,26 @@ def _create_trunk(port, network, **kwargs):
self.assertEqual({'inside': 42, 'outside': 1234}, swcfg.ifaces[0].vlan_translations[0].dict())

def test_bind_port_trunking_without_subport_direct_level_1(self):
fake_segments = [{'id': 'fake-segment-id', 'physical_network': 'cat', 'segmentation_id': 42,
'network_type': 'vlan'}]
binding_levels = [{'driver': 'cc-fabric', 'bound_segment': self._vxlan_segment}]
with mock.patch.object(CCFabricSwitchAgentRPCClient, 'apply_config_update') as mock_acu:
context = self._test_bind_port(fake_host='node101-cat',
fake_segments=fake_segments, binding_levels=binding_levels)
context.continue_binding.assert_not_called()
mock_acu.assert_called()
context.set_binding.assert_called()

# check config
swcfg = mock_acu.call_args[0][1]
self.assertEqual(agent_msg.OperationEnum.add, swcfg[0].operation)
self.assertEqual(1, len(swcfg))
self.assertEqual("cat-sw1", swcfg[0].switch_name)
self.assertEqual('add', swcfg[0].operation.name)
self.assertIsNone(swcfg[0].ifaces[0].native_vlan)
self.assertEqual([42], swcfg[0].ifaces[0].trunk_vlans)

def test_bind_port_direct_no_native_vlan_if_multitrunk_set(self):
fake_segments = [{'id': 'fake-segment-id', 'physical_network': 'seagull', 'segmentation_id': 42,
'network_type': 'vlan'}]
binding_levels = [{'driver': 'cc-fabric', 'bound_segment': self._vxlan_segment}]
Expand Down
3 changes: 2 additions & 1 deletion networking_ccloud/tools/netbox_config_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,8 @@ def get_connected_devices(self, switches: List[NbRecord]) -> Tuple[Set[NbRecord]

hgs = [conf.Hostgroup(binding_hosts=[h.name], direct_binding=True, members=self.sort_switchports(m),
infra_networks=sorted(device_infra_nets_map[h][0], key=lambda x: x.vlan),
extra_vlans=sorted(device_infra_nets_map[h][1]) if device_infra_nets_map[h][1] else None)
extra_vlans=sorted(device_infra_nets_map[h][1]) if device_infra_nets_map[h][1] else None,
allow_multiple_trunk_ports=h.device_role.slug == 'dp-data-domain')
for h, m in device_ports_map.items()]
return set(device_ports_map.keys()), hgs

Expand Down

0 comments on commit df890d3

Please sign in to comment.