Skip to content

Commit 3cc4a53

Browse files
committed
Assign Dynamic Network Segments in each cabinet where a Network is bound
The old scheme used a Nautobot plugin to assign a VLAN number outside of Openstack, and this is what was configured on the switch. This caused issues because Neutron had no knowledge of these VLANs, which make it difficult to have Neutron drive any layer 3 services or even trunk ports. Now we have Neutron allocate "Dynamic" network segments instead, and these are pushed to Nautobot, to keep Nautobot's VLANs in sync with Neutron's Network Segments. To facilitate we are using the "physical network" of each Ironic baremetal port to identify the vlan group where that switch port lives. For every vlan group we have created a Network Segment Range with the same name. Dynamic network segments of type "VLAN" are assigned from those ranges. We implement some new API calls to replace the "prep switch interface" and "detach port" Nautobot jobs which were previously making interface updates in addition to assigning VLAN numbers. Specifically, we now update the vlans on the switch port and we toggle it's "provisioning" state. Undersync is invoked with a VLAN Group parameter. We now identify the VLAN Group by name instead of UUID. The name is directly discoverable from Neutron whereas the UUID would have to be looked up in Nautobot. (Undersync was already updated to accept either type of parameter.) In Neutron we now exclusively use type "VXLAN" and so all support for type "VLAN" Networks has been removed. This also includes the "provisioning" network which has been recreated as type VXLAN in our environment. There is an opportunity to remove our special "vlan 4010" case handling of the provisioning network and have this created by Neutron using the same mechanisms as for as any other tenant network, but that has not happened yet. Trunk ports have first-class support in Neutron but they are handled by a separate model, disjoint from "normal" ports, and using trunk ports exercises totally different ml2 driver callbacks. Trunk port support is coming in a separate future PR.
1 parent acbf30d commit 3cc4a53

File tree

8 files changed

+364
-525
lines changed

8 files changed

+364
-525
lines changed

python/neutron-understack/neutron_understack/nautobot.py

Lines changed: 77 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -212,49 +212,88 @@ def add_tenant_vlan_tag_to_ucvni(self, network_uuid: str, vlan_tag: int) -> dict
212212
def subnet_delete(self, uuid: str) -> dict:
213213
return self.make_api_request("DELETE", f"/api/ipam/prefixes/{uuid}/")
214214

215-
def prep_switch_interface(
215+
def configure_port_status(self, interface_uuid: str, status: str) -> dict:
216+
url = f"/api/dcim/interfaces/{interface_uuid}/"
217+
payload = {"status": {"name": status}}
218+
return self.make_api_request("PATCH", url, payload)
219+
220+
def set_port_vlan_associations(
216221
self,
217-
connected_interface_id: str,
218-
ucvni_uuid: str,
219-
vlan_tag: int | None,
220-
modify_native_vlan: bool | None = True,
222+
interface_uuid: str,
223+
native_vlan_id: int | None,
224+
allowed_vlans_ids: set[int],
225+
vlan_group_name: str,
221226
) -> dict:
222-
"""Runs a Nautobot Job to update a switch interface for tenant mode.
223-
224-
The nautobot job will assign vlans as required and set the interface
225-
into the correct mode for "normal" tenant operation.
227+
"""Set the tagged and untagged vlan(s) on an interface."""
228+
url = f"/api/dcim/interfaces/{interface_uuid}/"
226229

227-
The dictionary with vlan group ID and vlan tag is returned.
228-
"""
229-
url = "/api/plugins/undercloud-vni/prep_switch_interface"
230-
payload = {
231-
"ucvni_id": str(ucvni_uuid),
232-
"connected_interface_id": str(connected_interface_id),
233-
"modify_native_vlan": modify_native_vlan,
234-
"vlan_tag": vlan_tag,
230+
payload: dict = {
231+
"tagged_vlans": [
232+
_vlan_payload(vlan_group_name, vlan_id) for vlan_id in allowed_vlans_ids
233+
],
235234
}
236-
return self.make_api_request("POST", url, payload)
237235

238-
def detach_port(self, connected_interface_id: str, ucvni_uuid: str) -> str:
239-
"""Runs a Nautobot Job to cleanup a switch interface.
236+
if native_vlan_id is not None:
237+
payload["untagged_vlan"] = _vlan_payload(vlan_group_name, native_vlan_id)
240238

241-
The nautobot job will find a VLAN that is bound to the UCVNI, remove it
242-
from the Interface and if the VLAN is unused it will delete it.
239+
return self.make_api_request("PATCH", url, payload)
240+
241+
def add_port_vlan_associations(
242+
self,
243+
interface_uuid: str,
244+
allowed_vlans_ids: set[int],
245+
vlan_group_name: str,
246+
) -> dict:
247+
"""Adds the specified vlan(s) to interface untagged/tagged vlans."""
248+
url = f"/api/dcim/interfaces/{interface_uuid}/"
249+
250+
current_state = self.make_api_request("GET", f"{url}?depth=1")
251+
252+
current_tagged_vlans = {
253+
tagged_vlan["vid"] for tagged_vlan in current_state.get("tagged_vlans", [])
254+
}
255+
256+
tagged_vlans = current_tagged_vlans.union(allowed_vlans_ids)
243257

244-
The vlan group ID is returned.
245-
"""
246-
url = "/api/plugins/undercloud-vni/detach_port"
247258
payload = {
248-
"ucvni_uuid": str(ucvni_uuid),
249-
"connected_interface_id": str(connected_interface_id),
259+
"tagged_vlans": [
260+
_vlan_payload(vlan_group_name, vlan_id) for vlan_id in tagged_vlans
261+
],
250262
}
251-
resp_data = self.make_api_request("POST", url, payload)
263+
return self.make_api_request("PATCH", url, payload)
252264

253-
return resp_data["vlan_group_id"]
265+
def remove_port_network_associations(
266+
self, interface_uuid: str, network_ids_to_remove: set[str]
267+
):
268+
query = """
269+
query($interface_id: ID!){
270+
interface(id: $interface_id){
271+
name
272+
untagged_vlan {id network: rel_ucvni_vlans { id }}
273+
tagged_vlans {id network: rel_ucvni_vlans { id }}
274+
}
275+
}
276+
"""
277+
variables = {"interface_id": interface_uuid}
278+
current = self.api.graphql.query(query, variables).json["data"]["interface"]
279+
LOG.debug("Nautobot %s query result: %s", variables, current)
254280

255-
def configure_port_status(self, interface_uuid: str, status: str) -> dict:
256281
url = f"/api/dcim/interfaces/{interface_uuid}/"
257-
payload = {"status": {"name": status}}
282+
payload = {}
283+
284+
current_untagged_network = current["untagged_vlan"]["network"]["id"]
285+
if current_untagged_network in network_ids_to_remove:
286+
payload["untagged_vlan"] = None
287+
288+
payload["tagged_vlans"] = [
289+
tagged_vlan["id"]
290+
for tagged_vlan in current["tagged_vlans"]
291+
if (
292+
tagged_vlan.get("network")
293+
and tagged_vlan.get("network")["id"] not in network_ids_to_remove
294+
)
295+
]
296+
258297
return self.make_api_request("PATCH", url, payload)
259298

260299
def fetch_vlan_group_uuid(self, device_uuid: str) -> str:
@@ -303,3 +342,10 @@ def create_vlan_and_associate_vlan_to_ucvni(self, vlan: VlanPayload):
303342
) from error
304343
else:
305344
return result
345+
346+
347+
def _vlan_payload(vlan_group_name: str, vlan_id: int) -> dict:
348+
return {
349+
"vlan_group": {"name": vlan_group_name},
350+
"vid": vlan_id,
351+
}

0 commit comments

Comments
 (0)