Skip to content

Commit 418d8a6

Browse files
authored
Merge pull request #267 from atlanticwave-sdx/266-request-on-the-same-switch
request with the same port (invalid) or the same node (just vlan assi…
2 parents 457c4f5 + 9d238ee commit 418d8a6

File tree

4 files changed

+194
-3
lines changed

4 files changed

+194
-3
lines changed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
44

55
[project]
66
name = "sdx-pce"
7-
version = "3.0.0.dev9"
7+
version = "3.0.0.dev10"
88
description = "Heuristic and Optimal Algorithms for CSP and TE Computation"
99
authors = [
1010
{ name = "Yufeng Xin", email = "yxin@renci.org" },

src/sdx_pce/topology/temanager.py

+100-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from sdx_pce.utils.constants import Constants
3030
from sdx_pce.utils.exceptions import (
3131
RequestValidationError,
32+
SameSwitchRequestError,
3233
TEError,
3334
UnknownRequestError,
3435
ValidationError,
@@ -412,6 +413,14 @@ def generate_traffic_matrix(self, connection_request: dict) -> TrafficMatrix:
412413
f"ingress_port.id: {ingress_port.id}, "
413414
f"egress_port.id: {egress_port.id}"
414415
)
416+
if ingress_port.id == egress_port.id:
417+
self._logger.warning(
418+
f"Source and destination ports are the same: {ingress_port.id}"
419+
)
420+
raise RequestValidationError(
421+
f"Source and destination ports are the same: {ingress_port.id}",
422+
400,
423+
)
415424

416425
topology = self.topology_manager.get_topology()
417426

@@ -430,6 +439,24 @@ def generate_traffic_matrix(self, connection_request: dict) -> TrafficMatrix:
430439
)
431440
return None
432441

442+
if ingress_node == egress_node:
443+
self._logger.warning(
444+
f"Source and destination nodes are the same: {ingress_node.id}"
445+
)
446+
domain_id = self.topology_manager.get_domain_name(ingress_node.id)
447+
ingress_user_port_tag = ingress_port.vlan_range
448+
egree_user_port_tag = egress_port.vlan_range
449+
self._logger.info(f"Same switch request: {domain_id}")
450+
raise SameSwitchRequestError(
451+
f"Source and destination nodes are the same: {ingress_node.id}",
452+
request.id,
453+
domain_id,
454+
ingress_port.id,
455+
egress_port.id,
456+
ingress_user_port_tag,
457+
egree_user_port_tag,
458+
)
459+
433460
ingress_nodes = [
434461
x for x, y in self.graph.nodes(data=True) if y["id"] == ingress_node.id
435462
]
@@ -581,6 +608,77 @@ def add_breakdowns_to_connection(self, connection_request: dict, breakdowns: dic
581608

582609
return connection_request
583610

611+
# Special case: endpoints on the same device, no need to call the solver, only need vlan assignment
612+
def generate_connection_breakdown_same_switch(
613+
self,
614+
request_id,
615+
domain,
616+
ingress_port_id: str,
617+
egress_port_id: str,
618+
ingress_port_tag,
619+
egress_port_tag,
620+
):
621+
"""
622+
Generate a breakdown for a connection request where the source and destination ports are the same.
623+
"""
624+
ingress_port = self.topology_manager.get_port_by_id(ingress_port_id)
625+
egress_port = self.topology_manager.get_port_by_id(egress_port_id)
626+
627+
self._logger.debug(
628+
f"ingress_port: {ingress_port_id}, egress_port: {egress_port_id}, ingress_port_tag: {ingress_port_tag}, egress_port_tag: {egress_port_tag}"
629+
)
630+
631+
ingress_vlan = self._reserve_vlan(
632+
domain,
633+
ingress_port,
634+
request_id,
635+
ingress_port_tag,
636+
None,
637+
)
638+
639+
egress_vlan = self._reserve_vlan(
640+
domain,
641+
egress_port,
642+
request_id,
643+
egress_port_tag,
644+
None,
645+
)
646+
647+
if ingress_vlan is None or egress_vlan is None:
648+
self._logger.error(
649+
f"ingress_vlan: {ingress_vlan}, egress_vlan: {egress_vlan}. "
650+
f"Can't proceed. Rolling back reservations."
651+
)
652+
self.unreserve_vlan(request_id=request_id)
653+
raise TEError(f"Can't find a vlan assignment for: {request_id}", 410)
654+
655+
self._logger.debug(f"ingress_vlan: {ingress_vlan}, egress_vlan: {egress_vlan}")
656+
657+
tag_type = 0 if ingress_vlan == "untagged" else 1
658+
port_a = VlanTaggedPort(
659+
VlanTag(value=ingress_vlan, tag_type=tag_type), port_id=ingress_port_id
660+
)
661+
tag_type = 0 if egress_vlan == "untagged" else 1
662+
port_z = VlanTaggedPort(
663+
VlanTag(value=egress_vlan, tag_type=tag_type), port_id=egress_port_id
664+
)
665+
666+
# Names look like "AMLIGHT_vlan_201_202_Ampath_Tenet". We
667+
# can form the initial part, but where did the
668+
# `Ampath_Tenet` at the end come from?
669+
domain_name = domain.split(":")[-1].split(".")[0].upper()
670+
name = f"{domain_name}_vlan_{ingress_vlan}_{egress_vlan}"
671+
breakdowns = {}
672+
breakdown = VlanTaggedBreakdown(
673+
name=name,
674+
dynamic_backup_path=True,
675+
uni_a=port_a,
676+
uni_z=port_z,
677+
)
678+
breakdowns[domain] = breakdown.to_dict()
679+
return breakdowns
680+
681+
# General case
584682
def generate_connection_breakdown(
585683
self, solution: ConnectionSolution, connection_request: dict
586684
) -> dict:
@@ -1236,7 +1334,7 @@ def _reserve_vlan(
12361334
# with self._topology_lock:
12371335
# pass
12381336

1239-
self._logger.debug(
1337+
self._logger.info(
12401338
f"Reserving VLAN for domain: {domain}, port: {port}, "
12411339
f"request_id: {request_id}, tag:{tag}"
12421340
)
@@ -1262,6 +1360,7 @@ def _reserve_vlan(
12621360
self._logger.warning(
12631361
f"Can't find a VLAN table for domain: {domain} port: {port_id}"
12641362
)
1363+
self._logger.warning(f"Available ports: {domain_table.keys()}")
12651364
return None
12661365

12671366
if tag is None:

src/sdx_pce/utils/exceptions.py

+28
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,34 @@ def __init__(self, message: str, request_id: str):
2121
self.request_id = request_id
2222

2323

24+
class SameSwitchRequestError(Exception):
25+
"""
26+
A custom exception to represent a request with endpoints on the same switch.
27+
"""
28+
29+
def __init__(
30+
self,
31+
message: str,
32+
request_id: str,
33+
domain_id: str,
34+
ingress_port: str,
35+
egress_port: str,
36+
ingress_user_port_tag,
37+
egress_user_port_tag,
38+
):
39+
"""
40+
:param message: a string containing the error message.
41+
:param request_id: a string containing request ID.
42+
"""
43+
super().__init__(f"{message} (ID: {request_id})")
44+
self.request_id = request_id
45+
self.domain_id = domain_id
46+
self.ingress_port = ingress_port
47+
self.egress_port = egress_port
48+
self.ingress_user_port_tag = ingress_user_port_tag
49+
self.egress_user_port_tag = egress_user_port_tag
50+
51+
2452
class RequestValidationError(Exception):
2553
"""
2654
A custom exception to represent TE (Traffic Engineering) errors.

tests/test_te_manager.py

+65-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from sdx_pce.topology.temanager import TEManager
1010
from sdx_pce.utils.exceptions import (
1111
RequestValidationError,
12+
SameSwitchRequestError,
1213
TEError,
1314
UnknownRequestError,
1415
ValidationError,
@@ -334,7 +335,10 @@ def test_generate_graph_and_connection_with_sax_2_invalid(self):
334335
# unresolvable port IDs, which are not present in the given
335336
# topology.
336337
request = json.loads(TestData.CONNECTION_REQ_FILE_SAX_2_INVALID.read_text())
337-
tm = temanager.generate_traffic_matrix(request)
338+
tm = None
339+
with self.assertRaises(RequestValidationError) as ctx:
340+
tm = temanager.generate_traffic_matrix(request)
341+
print(f"{ctx.exception}")
338342
self.assertIsNone(tm)
339343

340344
def test_generate_graph_and_connection_with_sax_2_valid(self):
@@ -438,6 +442,66 @@ def test_connection_amlight_v2(self):
438442
# (ampath1, ampath3), (ampath3, ampath2)
439443
self.assertEqual(len(next(iter(solution.connection_map.values()))), 2)
440444

445+
def test_connection_amlight_v2_with_same_port_invalid(self):
446+
"""
447+
Test with just one topology/domain.
448+
"""
449+
topology = json.loads(TestData.TOPOLOGY_FILE_AMLIGHT_v2.read_text())
450+
temanager = TEManager(topology_data=topology)
451+
graph = temanager.generate_graph_te()
452+
453+
self.assertIsInstance(graph, nx.Graph)
454+
455+
request = {
456+
"name": "new-connection",
457+
"id": "123",
458+
"endpoints": [
459+
{"port_id": "urn:sdx:port:ampath.net:Ampath1:50", "vlan": "777"},
460+
{"port_id": "urn:sdx:port:ampath.net:Ampath1:50", "vlan": "777"},
461+
],
462+
"qos_metrics": {},
463+
"scheduling": {},
464+
}
465+
with self.assertRaises(RequestValidationError) as ctx:
466+
temanager.generate_traffic_matrix(request)
467+
print(f"{ctx.exception}")
468+
469+
def test_connection_amlight_v2_with_same_node_valid(self):
470+
"""
471+
Test with just one topology/domain.
472+
"""
473+
topology = json.loads(TestData.TOPOLOGY_FILE_AMLIGHT_v2.read_text())
474+
temanager = TEManager(topology_data=topology)
475+
graph = temanager.generate_graph_te()
476+
477+
self.assertIsInstance(graph, nx.Graph)
478+
479+
request = {
480+
"name": "new-connection",
481+
"id": "123",
482+
"endpoints": [
483+
{"port_id": "urn:sdx:port:ampath.net:Ampath1:40", "vlan": "untagged"},
484+
{"port_id": "urn:sdx:port:ampath.net:Ampath1:50", "vlan": "777"},
485+
],
486+
"qos_metrics": {},
487+
"scheduling": {},
488+
}
489+
try:
490+
temanager.generate_traffic_matrix(request)
491+
except SameSwitchRequestError as ctx:
492+
print(
493+
f"{str(ctx)},{ctx.request_id},{ctx.domain_id},{ctx.ingress_port},{ctx.egress_port}, {ctx.ingress_user_port_tag}, {ctx.egress_user_port_tag}"
494+
)
495+
breakdown = temanager.generate_connection_breakdown_same_switch(
496+
ctx.request_id,
497+
ctx.domain_id,
498+
ctx.ingress_port,
499+
ctx.egress_port,
500+
ctx.ingress_user_port_tag,
501+
ctx.egress_user_port_tag,
502+
)
503+
print(f"Breakdown: {breakdown}")
504+
441505
def test_connection_amlight_user_port(self):
442506
"""
443507
Test with just one topology/domain.

0 commit comments

Comments
 (0)