Skip to content

Commit

Permalink
Merge pull request #253 from atlanticwave-sdx/245-a-higher-level-api-…
Browse files Browse the repository at this point in the history
…for-state-management

245 a higher level api for state management
  • Loading branch information
YufengXin authored Feb 6, 2025
2 parents 8af1114 + c7fd64e commit ec81397
Show file tree
Hide file tree
Showing 13 changed files with 354 additions and 49 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ jobs:
- name: Check out sources
uses: actions/checkout@v4

- name: Set up Python 3.9
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.9"
python-version: "3.11"
cache: 'pip' # cache pip dependencies
cache-dependency-path: pyproject.toml

Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ jobs:
fail-fast: false
matrix:
python-version:
- "3.9"
- "3.10"
- "3.11"
- "3.12"

Expand Down
6 changes: 3 additions & 3 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.dev7"
version = "3.0.0.dev8"
description = "Heuristic and Optimal Algorithms for CSP and TE Computation"
authors = [
{ name = "Yufeng Xin", email = "[email protected]" },
Expand All @@ -14,7 +14,7 @@ authors = [
{ name = "Sajith Sasidharan", email = "[email protected]" },
]
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.11"
license = {file = "LICENSE"}
classifiers = [
"Programming Language :: Python :: 3",
Expand All @@ -29,7 +29,7 @@ dependencies = [
"prtpy",
"pydot",
"dataclasses-json",
"sdx-datamodel @ git+https://github.com/atlanticwave-sdx/[email protected].dev3",
"sdx-datamodel @ git+https://github.com/atlanticwave-sdx/[email protected].dev4",
]

[project.urls]
Expand Down
29 changes: 29 additions & 0 deletions src/sdx_pce/topology/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ def set_topology(self, topology):
def get_topology(self):
return self._topology

def get_topology_dict(self):
return self._topology.to_dict()

def get_topology_map(self) -> dict:
return self._topology_map

Expand Down Expand Up @@ -517,6 +520,22 @@ def change_link_property_by_value(self, port_id_0, port_id_1, property, value):
)
# 2.2 need to change the sub_ver of the topology?

def change_port_vlan_range(self, topology_id, port_id, value):
topology = self._topology_map.get(topology_id)
port = self.get_port_obj_by_id(topology, port_id)
if port is None:
self._logger.debug(f"Port not found in changing vlan range:{port_id}")
return None
self._logger.debug(f"Port found:{port_id};new vlan range:{value}")
services = port.__getattribute__(Constants.SERVICES)
if services:
l2vpn_ptp = services.__getattribute__(Constants.L2VPN_P2P)
if l2vpn_ptp:
l2vpn_ptp["vlan_range"] = value
self._logger.info(
"updated the port:" + port_id + " vlan_range" + " to " + str(value)
)

def update_element_property_json(self, data, element, element_id, property, value):
elements = data[element]
for element in elements:
Expand All @@ -542,6 +561,16 @@ def get_port_by_id(self, port_id: str):
return port.to_dict()
return None

def get_port_obj_by_id(self, topology, port_id: str):
"""
Given port id, returns a Port.
"""
for node in topology.nodes:
for port in node.ports:
if port.id == port_id:
return port
return None

def are_two_ports_same_domain(self, port1_id: str, port2_id: str):
"""
Check if two ports are in the same domain.
Expand Down
114 changes: 99 additions & 15 deletions src/sdx_pce/topology/temanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
from networkx.algorithms import approximation as approx
from sdx_datamodel.models.port import Port
from sdx_datamodel.parsing.connectionhandler import ConnectionHandler
from sdx_datamodel.parsing.exceptions import (
MissingAttributeException,
ServiceNotSupportedException,
)
from sdx_datamodel.validation.connectionvalidator import ConnectionValidator

from sdx_pce.models import (
Expand Down Expand Up @@ -175,6 +179,73 @@ def vlan_tags_table(self, table: dict):

self._vlan_tags_table = table

def update_available_vlans(self):
"""
Update the available VLAN ranges for each domain and port.
This method iterates through the VLAN tags table and identifies the available VLANs (those with status UNUSED_VLAN).
It then groups consecutive VLANs into ranges and updates the new VLAN ranges for each domain and port.
Returns:
dict: A dictionary containing the updated VLAN ranges for each domain and port.
Example:
{
'domain1': {
'port1': ['1-5', '7', '10-12'],
'port2': ['3-4', '6']
},
'domain2': {
'port1': ['2-3', '8-10']
}
}
Note:
- The method assumes that the VLAN tags table is a dictionary with the structure:
{
'domain': {
'port_id': {
vlan_id: status,
...
},
...
},
...
}
- The status UNUSED_VLAN should be defined elsewhere in the code.
- The method logs the updated VLAN ranges using the class's logger.
"""

new_vlan_ranges = {}

for domain, ports in self._vlan_tags_table.items():
new_vlan_ranges[domain] = {}
for port_id, vlans in ports.items():
available_vlans = [
vlan for vlan, status in vlans.items() if status == UNUSED_VLAN
]
if available_vlans:
ranges = []
start = end = available_vlans[0]
for vlan in available_vlans[1:]:
if vlan == end + 1:
end = vlan
else:
if start == end:
ranges.append(f"{start}")
else:
ranges.append(f"{start}-{end}")
start = end = vlan
if start == end:
ranges.append(f"{start}")
else:
ranges.append(f"{start}-{end}")
new_vlan_ranges[domain][port_id] = ranges

# Update the 'vlan_range' property of the 'service' property in the corresponding port
self.topology_manager.change_port_vlan_range(
domain, port_id, ranges
)

self._logger.info(f"Updated VLAN ranges: {new_vlan_ranges}")
return new_vlan_ranges

def _update_vlan_tags_table(self, domain_name: str, port_map: dict):
"""
Update VLAN tags table in a non-disruptive way, meaning: only add new
Expand Down Expand Up @@ -264,30 +335,37 @@ def generate_traffic_matrix(self, connection_request: dict) -> TrafficMatrix:
f"generate_traffic_matrix: connection_request: {connection_request}"
)

request = ConnectionHandler().import_connection_data(connection_request)
try:
request = ConnectionHandler().import_connection_data(connection_request)
except MissingAttributeException as e:
self._logger.error(f"Missing attribute: {e} for {connection_request}")
raise RequestValidationError(
f"Validation error: {e} for {connection_request}", 400
)
except ServiceNotSupportedException as e:
self._logger.error(f"Service not supported: {e} for {connection_request}")
raise RequestValidationError(
f"Validation error: {e} for {connection_request}", 402
)

try:
ConnectionValidator(request).is_valid()
except RequestValidationError as request_err:
except ValueError as request_err:
err = traceback.format_exc().replace("\n", ", ")
self._logger.error(
f"Validation error: {request_err} for {connection_request}"
f"Validation error: {request_err} for {connection_request}: {request_err} - {err}"
)
if "Strict QoS requirements" in str(request_err):
raise RequestValidationError(
f"Validation error: {request_err} for {connection_request}", 410
)
if "Scheduling" in str(request_err):
raise RequestValidationError(
f"Validation error: {request_err} for {connection_request}", 411
)
raise RequestValidationError(
f"Validation error: {request_err} for {connection_request}", 400
)
except ServiceNotSupportedException as e:
self._logger.error(f"Service not supported: {e} for {connection_request}")
raise RequestValidationError(
f"Validation error: {e} for {connection_request}", 402
)
except Exception as e:
err = traceback.format_exc().replace("\n", ", ")
self._logger.error(
f"Error when generating/publishing breakdown: {e} - {err}"
)
self._logger.error(f"Error when validating connection request: {e} - {err}")
raise RequestValidationError(
f"Validation error: {e} for {connection_request}", 400
)
Expand Down Expand Up @@ -684,7 +762,7 @@ def generate_connection_breakdown(
)

# Make tests pass, temporarily.
# ToDo: need to throw an exception if tagged_breakdown is None
# need to throw an exception if tagged_breakdown is None
if tagged_breakdown is None:
raise TEError(
f"Can't find a valid vlan breakdown solution for: {connection_request}",
Expand All @@ -700,6 +778,9 @@ def generate_connection_breakdown(
# Now it is the time to update the bandwidth of the links after breakdowns are successfully generated
self.update_link_bandwidth(solution, reduce=True)

# Update available VLANs
self.update_available_vlans()

# keep the connection solution for future reference
self._connectionSolution_list.append(solution)

Expand Down Expand Up @@ -1256,6 +1337,9 @@ def delete_connection(self, request_id: str):
# Now it is the time to update the bandwidth of the links after breakdowns are successfully generated
self.update_link_bandwidth(solution, reduce=False)

# Update available VLANs
self.update_available_vlans()

def get_connection_solution(self, request_id: str) -> Optional[ConnectionSolution]:
"""
Get a connection solution by request ID.
Expand Down
3 changes: 3 additions & 0 deletions src/sdx_pce/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ class Constants:
PACKET_LOSS = "packet_loss"
AVAILABILITY = "availability"
WEIGHT = "weight"
SERVICES = "services"
L2VPN_P2P = "l2vpn_ptp"
L2VPN_P2MP = "l2vpn_ptmp"

OBJECTIVE_COST = 0
OBJECTIVE_LOAD_BALANCING = 1
Expand Down
6 changes: 2 additions & 4 deletions tests/data/sax-2-request-invalid.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,23 @@
"id": "id",
"name": "urn:sdx:port:amlight.net:Novi06:10",
"node": "urn:sdx:port:amlight.net:Novi06",
"vlan_range": 150,
"vlan_range": "150",
"short_name": "10",
"state": "enabled",
"status": "up"
},
"end_time": "2030-01-23T04:56:07+00:00",
"id": "id",
"ingress_port": {
"id": "id",
"name": "urn:sdx:port:amlight.net:Novi06:9",
"node": "urn:sdx:port:amlight.net:Novi06",
"vlan_range": 150,
"vlan_range": "150",
"short_name": "9",
"state": "enabled",
"status": "up"
},
"name": "name",
"quantity": 0,
"start_time": "2030-01-23T04:56:07+00:00",
"status": "success",
"timestamp": "2030-01-23T04:56:07+00:00",
"version": 1
Expand Down
6 changes: 2 additions & 4 deletions tests/data/sax-2-request-valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,23 @@
"id": "urn:sdx:port:sax:B1:1",
"name": "Novi01:1",
"node": "urn:sdx:node:sax:B1",
"vlan_range": 150,
"vlan_range": "150",
"short_name": "10",
"state": "enabled",
"status": "up"
},
"end_time": "2030-01-23T04:56:07+00:00",
"id": "id",
"ingress_port": {
"id": "urn:sdx:port:sax:B2:1",
"name": "Novi02:1",
"node": "urn:sdx:node:sax:B2",
"vlan_range": 150,
"vlan_range": "150",
"short_name": "9",
"state": "enabled",
"status": "up"
},
"name": "name",
"quantity": 0,
"start_time": "2030-01-23T04:56:07+00:00",
"status": "success",
"timestamp": "2030-01-23T04:56:07+00:00",
"version": 1
Expand Down
6 changes: 2 additions & 4 deletions tests/data/test_request_amlight.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
{
"id": "id",
"name": "AMLight",
"start_time": "2030-01-23T04:56:07.000",
"end_time": "2030-01-23T04:56:07.000",
"bandwidth_required": 100,
"latency_required": 20,
"egress_port":
{
"id": "urn:sdx:port:amlight.net:A1:1",
"name": "Novi100:1",
"vlan_range": 150
"vlan_range": "150"
},
"ingress_port":
{
"id": "urn:sdx:port:amlight.net:B1:2",
"name": "Novi100:2",
"vlan_range": 150
"vlan_range": "150"
}
}
6 changes: 2 additions & 4 deletions tests/data/test_request_amlight_user_port.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
{
"id": "id",
"name": "AMLight",
"start_time": "2030-01-23T04:56:07.000",
"end_time": "2030-01-23T04:56:07.000",
"bandwidth_required": 100,
"latency_required": 20,
"egress_port":
{
"id": "urn:sdx:port:amlight.net:A1:3",
"name": "Novi100:1",
"vlan_range": 150
"vlan_range": "150"
},
"ingress_port":
{
"id": "urn:sdx:port:amlight.net:B1:2",
"name": "Novi100:2",
"vlan_range": 160
"vlan_range": "160"
}
}
Loading

0 comments on commit ec81397

Please sign in to comment.