Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle VLAN ranges in connection requests #240

Merged
merged 56 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
9578276
Remove old setuptools_scm configuration
sajith Oct 28, 2024
efe27ad
Drop Python 3.8 compatibility import
sajith Oct 28, 2024
a41674b
Add a test for requests containing VLAN ranges
sajith Oct 28, 2024
366301e
Use correct comment
sajith Oct 28, 2024
afcb698
Run isort
sajith Oct 28, 2024
9163cec
Attempt to reserve VLAN range
sajith Oct 30, 2024
d692fa6
Check shape of breakdown
sajith Oct 30, 2024
664fa1a
Do regex match only against strings
sajith Oct 30, 2024
40eaef7
Move import to top-level
sajith Oct 30, 2024
2d8d4dd
Use correct parameter in docstring
sajith Oct 31, 2024
a6c2428
Use v2 topology
sajith Oct 31, 2024
7bf169a
Request connection between user ports
sajith Oct 31, 2024
38c2a9c
Solve the mystery of wrong connection breakdown
sajith Oct 31, 2024
539cb26
Return just the requested tag when a range is requested
sajith Oct 31, 2024
a35ee7b
Remove stub method
sajith Nov 1, 2024
b535ae6
Rename VLAN range test
sajith Nov 1, 2024
0a36b78
Update comment
sajith Nov 1, 2024
ad32aef
Add more asserts
sajith Nov 1, 2024
8d69ed6
Localize all TEManager instances in tests
sajith Nov 1, 2024
fd90ad5
Load topology data in one step
sajith Nov 4, 2024
bf7bae4
Add a multi-domain multi-VLAN test stub
sajith Nov 6, 2024
068d613
Remove shadowed temanager
sajith Nov 8, 2024
713f92b
Trim trailing whitespace
sajith Nov 8, 2024
fdce39a
Use a dict to represent connection request
sajith Nov 8, 2024
25e4f73
Get a breakdown
sajith Nov 8, 2024
2214448
Add more assertions
sajith Nov 8, 2024
bdfb2e8
Try a connection request that is solvable
sajith Nov 8, 2024
3a633da
Do not validate port IDs until we have a known good request/solution
sajith Nov 8, 2024
ac27d58
Pass connection request around, not just request ID
sajith Nov 9, 2024
4b74b85
Generate a request ID for old-style connection requests
sajith Nov 9, 2024
e7c7715
Find common inter-domain VLAN ranges
sajith Nov 9, 2024
354c101
Use correct variable
sajith Nov 12, 2024
180ae9c
Do the check in two steps
sajith Nov 12, 2024
25601d3
Split error string
sajith Nov 12, 2024
b106f84
Add hints to error string
sajith Nov 12, 2024
5ee60e8
Update comment
sajith Nov 12, 2024
f94efb8
Update comment
sajith Nov 12, 2024
112ffbc
Remove TODO
sajith Nov 12, 2024
4c52007
Rename test
sajith Nov 12, 2024
8d4d969
Reduce log verbosity
sajith Nov 12, 2024
06b6ee6
Use a request that results in connections spanning three domains
sajith Nov 12, 2024
bab638b
Knock a couple of TODOs off
sajith Nov 12, 2024
46d38e6
Update asserts
sajith Nov 12, 2024
a8338b6
Check tenet part of the breakdown
sajith Nov 12, 2024
e1cd951
Use a different name and id in test request
sajith Nov 12, 2024
e794f19
Print before assert
sajith Nov 12, 2024
cb50348
Use simpler dict comparison in multi-domain vlan range test
sajith Nov 13, 2024
7037082
Use simpler dict comparison in single-domain vlan range test
sajith Nov 13, 2024
2f156c9
Simplify connection request in single-domain vlan range test
sajith Nov 13, 2024
77fc449
Remove redundant asserts
sajith Nov 13, 2024
e04b892
Add a test for anomalous result for later investigation
sajith Nov 13, 2024
3b4ce3b
Pacify the linter for now
sajith Nov 13, 2024
6a92ebf
throw an exception for failed valn assignment, instead of returning N…
YufengXin Nov 14, 2024
860373d
black
YufengXin Nov 14, 2024
a458943
Merge branch 'main' into 230.vlan-range
sajith Nov 14, 2024
fe7c20f
Bump version up
sajith Nov 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"

[project]
name = "sdx-pce"
version = "3.0.0.dev4"
version = "3.0.0.dev5"
description = "Heuristic and Optimal Algorithms for CSP and TE Computation"
authors = [
{ name = "Yufeng Xin", email = "[email protected]" },
Expand Down Expand Up @@ -67,12 +67,6 @@ filterwarnings = [
"ignore:Type has been deprecated in version 0.1.5 and will be removed:DeprecationWarning",
]


[tool.setuptools_scm]
# Write version info collected from git to a file. This happens when
# we run `python -m build`.
write_to = "src/sdx_pce/_version.py"

[tool.isort]
profile = "black"
src_paths = ["src", "tests", "scripts"]
Expand Down
136 changes: 124 additions & 12 deletions src/sdx_pce/topology/temanager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import re
import threading
from itertools import chain
from typing import List, Optional
Expand Down Expand Up @@ -600,7 +601,7 @@ def generate_connection_breakdown(

tagged_breakdown = self._reserve_vlan_breakdown(
domain_breakdown=domain_breakdown,
request_id=solution.request_id,
connection_request=connection_request,
ingress_user_port=ingress_user_port,
egress_user_port=egress_user_port,
)
Expand Down Expand Up @@ -680,7 +681,7 @@ def _get_ports_by_link(self, link: ConnectionPath):
def _reserve_vlan_breakdown(
self,
domain_breakdown: dict,
request_id: str,
connection_request: dict,
ingress_user_port=None,
egress_user_port=None,
) -> Optional[VlanTaggedBreakdowns]:
Expand Down Expand Up @@ -714,6 +715,18 @@ def _reserve_vlan_breakdown(

# if not, assuming vlan translation on the domain border port

self._logger.info(f"Assigning VLANs for request: {connection_request}")

# TODO: Generating a request_id locally is a workaround until
# we get rid of the old style connection request.
if isinstance(connection_request, dict):
request_id = connection_request.get("id")
else:
from uuid import uuid4

request_id = uuid4()
self._logger.warning(f"Generated ID {request_id} for old style request")

self._logger.info(
f"reserve_vlan_breakdown: domain_breakdown: {domain_breakdown}"
)
Expand All @@ -730,8 +743,13 @@ def _reserve_vlan_breakdown(

upstream_egress = segment.get("egress_port")
downstream_ingress = next_segment.get("ingress_port")

upstream_egress_vlan = self._find_common_vlan_on_link(
domain, upstream_egress["id"], next_domain, downstream_ingress["id"]
domain,
upstream_egress["id"],
next_domain,
downstream_ingress["id"],
connection_request,
)
self._logger.info(
f"upstream_egress_vlan: {upstream_egress_vlan}; upstream_egress: {upstream_egress}; downstream_ingress: {downstream_ingress}"
Expand Down Expand Up @@ -805,7 +823,9 @@ def _reserve_vlan_breakdown(
f"Can't proceed. Rolling back reservations."
)
self.unreserve_vlan(request_id=request_id)
return None
raise TEError(
f"Can't find a vlan assignment for: {connection_request}", 410
)

ingress_port_id = ingress_port["id"]
egress_port_id = egress_port["id"]
Expand Down Expand Up @@ -899,7 +919,12 @@ def _reserve_vlan_on_path(self, domain_breakdown, selected_vlan):
assert False, "Not implemented"

def _find_common_vlan_on_link(
self, domain, upstream_egress, next_domain, downstream_ingress
self,
domain,
upstream_egress,
next_domain,
downstream_ingress,
connection_request=None,
) -> Optional[str]:
"""
Find a common VLAN on the inter-domain link.
Expand All @@ -918,6 +943,59 @@ def _find_common_vlan_on_link(
downstream_vlan_table.keys()
)

self._logger.info(
f"Looking for common VLANS for connection_request: {connection_request}"
)

# TODO: shouldn't we update VLAN allocation tables here?

# The block below is a work-around to find out if a VLAN range
# was specified in the connection request, and then handle it
# accordingly. This code could probably be simplified if we
# use a "proper" data structure to represent the original
# connection request internally.
if connection_request and isinstance(connection_request, dict):
ingress_vlans_str = connection_request.get("ingress_port").get("vlan_range")
egress_vlans_str = connection_request.get("egress_port").get("vlan_range")

self._logger.info(
f"Found ingress_vlans: {ingress_vlans_str}, "
f"egress_vlans: {egress_vlans_str}"
)

# Do we have a range of VLANs to handle?
if self._tag_is_vlan_range(ingress_vlans_str):

# Both ingress and egress ranges should be the same
# for inter-domain links, since we (currently) infer
# it from the original request.
#
# It is quite unlikely that we'll ever get to this
# error state, but this is worth checking anyway.
if not self._tag_is_vlan_range(egress_vlans_str):
raise Exception(
f"VLAN range {ingress_vlans_str} requested on ingress, "
f"but not on egress (egress: {egress_vlans_str}"
)

start, end = map(int, ingress_vlans_str.split(":"))
vlans = list(range(start, end + 1))

for vlan in vlans:
if upstream_vlan_table[vlan] is not UNUSED_VLAN:
raise Exception(
f"Upstream VLAN {vlan} is in use; "
f"can't reserve {ingress_vlans_str} range"
)

if downstream_vlan_table[vlan] is not UNUSED_VLAN:
raise Exception(
f"Downstream VLAN {vlan} is in use; "
f"can't reserve {egress_vlans_str} range"
)

return ingress_vlans_str

for vlan in common_vlans:
if (
upstream_vlan_table[vlan] is UNUSED_VLAN
Expand All @@ -926,10 +1004,20 @@ def _find_common_vlan_on_link(
return vlan

self._logger.warning(
f"No common VLAN found between {domain} and {next_domain} for ports {upstream_egress} and {downstream_ingress}"
f"No common VLAN found between {domain} and {next_domain} "
f"for ports {upstream_egress} and {downstream_ingress}"
)
return None

def _tag_is_vlan_range(self, tag: str) -> bool:
"""
Return True if tag is of the form `n:m`
"""
if isinstance(tag, str):
return bool(re.match(r"\d+:\d+", tag))
else:
return False

def _reserve_vlan(
self,
domain: str,
Expand All @@ -951,9 +1039,10 @@ def _reserve_vlan(
preferences. See the description of "vlan" under
"Mandatory Attributes" section of the Service Provisioning
Data Model Specification 1.0 for details.
:param upstream_vlan: a string that contains the upstream tag to use

https://sdx-docs.readthedocs.io/en/latest/specs/provisioning-api-1.0.html#mandatory-attributes
:param upstream_egress_vlan: a string that contains the
upstream tag to use
"""

# with self._topology_lock:
Expand All @@ -979,7 +1068,7 @@ def _reserve_vlan(

vlan_table = domain_table.get(port_id)

self._logger.debug(f"reserve_vlan domain: {domain} vlan_table: {vlan_table}")
# self._logger.debug(f"reserve_vlan domain: {domain} vlan_table: {vlan_table}")

if vlan_table is None:
self._logger.warning(
Expand All @@ -998,6 +1087,29 @@ def _reserve_vlan(
for vlan_tag, vlan_usage in vlan_table.items():
if vlan_usage is UNUSED_VLAN:
available_tag = vlan_tag
elif self._tag_is_vlan_range(tag):
# expand the range.
start, end = map(int, tag.split(":"))
vlans = list(range(start, end + 1))

self._logger.debug(f"Attempting to reseve vlan range {vlans}")

# Check if all VLANs in the range are available.
for vlan in vlans:
if vlan_table[vlan] is not UNUSED_VLAN:
raise Exception(f"VLAN {vlan} is in use; can't reserve {tag} range")

# Mark range in use.
for vlan in vlans:
vlan_table[vlan] = request_id

# self._logger.debug(
# f"reserve_vlan domain {domain}, after reservation: "
# f"vlan_table: {vlan_table}, requested range: {tag}"
# )

# Return the tag to indicate success.
return tag
else:
if tag in vlan_table:
if vlan_table[tag] is UNUSED_VLAN:
Expand All @@ -1013,10 +1125,10 @@ def _reserve_vlan(
# mark the tag as in-use.
vlan_table[available_tag] = request_id

self._logger.debug(
f"reserve_vlan domain {domain}, after reservation: "
f"vlan_table: {vlan_table}, available_tag: {available_tag}"
)
# self._logger.debug(
# f"reserve_vlan domain {domain}, after reservation: "
# f"vlan_table: {vlan_table}, available_tag: {available_tag}"
# )

return available_tag

Expand Down
8 changes: 1 addition & 7 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import pathlib
import tempfile

try:
# Use stdlib modules with Python > 3.8.
from importlib.resources import files
except ImportError:
# Use compatibility library with Python 3.8.
from importlib_resources import files
from importlib.resources import files


class TestData:
Expand Down
Loading