diff --git a/pyproject.toml b/pyproject.toml index d6153022..f80f1973 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "prtpy", "pydot", "dataclasses-json", - "sdx-datamodel @ git+https://github.com/atlanticwave-sdx/datamodel@3.0.0.dev6", + "sdx-datamodel @ git+https://github.com/atlanticwave-sdx/datamodel@148.pydantic-connection-requests", ] [project.urls] diff --git a/src/sdx_pce/heuristic/heur.py b/src/sdx_pce/heuristic/heur.py index 5207606d..d79389a4 100644 --- a/src/sdx_pce/heuristic/heur.py +++ b/src/sdx_pce/heuristic/heur.py @@ -7,7 +7,7 @@ import prtpy from sdx_pce.load_balancing.te_solver import TESolver -from sdx_pce.models import ConnectionRequest, TrafficMatrix +from sdx_pce.models import PceConnectionRequest, TrafficMatrix from sdx_pce.utils.random_connection_generator import RandomConnectionGenerator from sdx_pce.utils.random_topology_generator import RandomTopologyGenerator @@ -41,7 +41,7 @@ def matrix_to_connection(matrix): """ traffic_matrix = TrafficMatrix(connection_requests=[]) for rq in matrix: - request = ConnectionRequest( + request = PceConnectionRequest( source=rq[0], destination=rq[1], required_bandwidth=rq[2], diff --git a/src/sdx_pce/models.py b/src/sdx_pce/models.py index 6a9440cc..24792515 100644 --- a/src/sdx_pce/models.py +++ b/src/sdx_pce/models.py @@ -6,7 +6,7 @@ @dataclass_json @dataclass(frozen=True) -class ConnectionRequest: +class PceConnectionRequest: """ A connection request. @@ -34,7 +34,7 @@ class TrafficMatrix: Traffic matrix is input to TE Solver. """ - connection_requests: List[ConnectionRequest] + connection_requests: List[PceConnectionRequest] request_id: str @@ -58,7 +58,7 @@ class ConnectionSolution: TE Solver's result is represented as a ConnectionSolution. """ - connection_map: Mapping[ConnectionRequest, List[ConnectionPath]] + connection_map: Mapping[PceConnectionRequest, List[ConnectionPath]] cost: float request_id: str diff --git a/src/sdx_pce/topology/temanager.py b/src/sdx_pce/topology/temanager.py index eca8af40..44a2cfae 100644 --- a/src/sdx_pce/topology/temanager.py +++ b/src/sdx_pce/topology/temanager.py @@ -1,24 +1,21 @@ import logging import re import threading -import traceback + +# import traceback from itertools import chain from typing import List, Optional import networkx as nx from networkx.algorithms import approximation as approx +from sdx_datamodel.models.connection_request import ConnectionRequest 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 ( ConnectionPath, - ConnectionRequest, ConnectionSolution, + PceConnectionRequest, TrafficMatrix, VlanTag, VlanTaggedBreakdown, @@ -35,6 +32,13 @@ ValidationError, ) +# from sdx_datamodel.parsing.exceptions import ( +# MissingAttributeException, +# ServiceNotSupportedException, +# ) +# from sdx_datamodel.validation.connectionvalidator import ConnectionValidator + + UNUSED_VLAN = None @@ -157,7 +161,7 @@ def get_failed_links(self) -> List[dict]: """Get failed links on the topology (ie., Links not up and enabled).""" return self.topology_manager.get_failed_links() - def get_connections(self) -> List[ConnectionRequest]: + def get_connections(self) -> List[PceConnectionRequest]: """Get all the connections in the _connectionSolution_list.""" connections = [] for solution in self._connectionSolution_list: @@ -369,39 +373,11 @@ def generate_traffic_matrix(self, connection_request: dict) -> TrafficMatrix: ) 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 ValueError as request_err: - err = traceback.format_exc().replace("\n", ", ") - self._logger.error( - f"Validation error: {request_err} for {connection_request}: {request_err} - {err}" - ) - 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 - ) + request = ConnectionRequest.model_validate(connection_request) except Exception as e: - err = traceback.format_exc().replace("\n", ", ") - self._logger.error(f"Error when validating connection request: {e} - {err}") - raise RequestValidationError( - f"Validation error: {e} for {connection_request}", 400 - ) + message = f"Validation error: for {connection_request}: {e}" + self._logger.error(message) + raise RequestValidationError(message, 400) self._logger.info(f"generate_traffic_matrix: decoded request: {request}") @@ -486,7 +462,7 @@ def generate_traffic_matrix(self, connection_request: dict) -> TrafficMatrix: f"required_bandwidth: {required_bandwidth}" ) - request = ConnectionRequest( + request = PceConnectionRequest( source=ingress_nodes[0], destination=egress_nodes[0], required_bandwidth=required_bandwidth, diff --git a/src/sdx_pce/utils/random_connection_generator.py b/src/sdx_pce/utils/random_connection_generator.py index 3b89d123..7c0a0b8b 100644 --- a/src/sdx_pce/utils/random_connection_generator.py +++ b/src/sdx_pce/utils/random_connection_generator.py @@ -1,6 +1,6 @@ import numpy as np -from sdx_pce.models import ConnectionRequest, TrafficMatrix +from sdx_pce.models import PceConnectionRequest, TrafficMatrix class RandomConnectionGenerator: @@ -34,7 +34,7 @@ def generate(self, querynum, l_bw, u_bw, l_lat, u_lat, seed=2022) -> TrafficMatr required_bandwidth = bw[i] required_latency = np.random.randint(l_lat, u_lat) - request = ConnectionRequest( + request = PceConnectionRequest( source=source, destination=destination, required_bandwidth=required_bandwidth, @@ -52,7 +52,7 @@ def generate(self, querynum, l_bw, u_bw, l_lat, u_lat, seed=2022) -> TrafficMatr required_bandwidth = bw[i] required_latency = np.random.randint(l_lat, u_lat) - request = ConnectionRequest( + request = PceConnectionRequest( source=source, destination=destination, required_bandwidth=required_bandwidth, diff --git a/tests/__init__.py b/tests/__init__.py index 0d1bf485..94cce1cd 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -16,12 +16,12 @@ class TestData: TOPOLOGY_FILE_AMLIGHT_USER_PORT = TOPOLOGY_DIR / "amlight_user_port.json" REQUESTS_DIR = files("sdx_datamodel") / "data" / "requests" - CONNECTION_REQ = REQUESTS_DIR / "test_request.json" - CONNECTION_REQ_AMLIGHT_ZAOXI_USER_PORT_v2 = ( - REQUESTS_DIR / "test_request-amlight_zaoxi-p2p-v2.json" + CONNECTION_REQ_v0 = REQUESTS_DIR / "v0" / "test_request.json" + CONNECTION_REQ_AMLIGHT_ZAOXI_USER_PORT_v1 = ( + REQUESTS_DIR / "v1.0" / "test-request-amlight-zaoxi-p2p.json" ) - CONNECTION_REQ_AMLIGHT_SAX_v2 = ( - REQUESTS_DIR / "test-request-amlight_sax-p2p-v2.json" + CONNECTION_REQ_AMLIGHT_SAX_v1 = ( + REQUESTS_DIR / "v1.0" / "test-request-amlight_sax-p2p-v2.json" ) # Write test output files in OS temporary directory. @@ -33,14 +33,14 @@ class TestData: # Other test data files. TEST_DATA_DIR = pathlib.Path(__file__).parent / "data" - CONNECTION_REQ_AMLIGHT = TEST_DATA_DIR / "test_request_amlight.json" - CONNECTION_REQ_AMLIGHT_USER_PORT = ( + CONNECTION_REQ_AMLIGHT_v0 = TEST_DATA_DIR / "test_request_amlight.json" + CONNECTION_REQ_AMLIGHT_USER_PORT_v0 = ( TEST_DATA_DIR / "test_request_amlight_user_port.json" ) - CONNECTION_REQ_AMLIGHT_ZAOXI_USER_PORT = ( + CONNECTION_REQ_AMLIGHT_ZAOXI_USER_PORT_v0 = ( TEST_DATA_DIR / "test_request_amlight_zaoxi_user_port.json" ) TOPOLOGY_FILE_SAX_2 = TEST_DATA_DIR / "sax-2.json" - CONNECTION_REQ_FILE_SAX_2_INVALID = TEST_DATA_DIR / "sax-2-request-invalid.json" - CONNECTION_REQ_FILE_SAX_2_VALID = TEST_DATA_DIR / "sax-2-request-valid.json" + CONNECTION_REQ_FILE_SAX_2_INVALID_v0 = TEST_DATA_DIR / "sax-2-request-invalid.json" + CONNECTION_REQ_FILE_SAX_2_VALID_v0 = TEST_DATA_DIR / "sax-2-request-valid.json" diff --git a/tests/test_te_manager.py b/tests/test_te_manager.py index 103ff398..4b47ad73 100644 --- a/tests/test_te_manager.py +++ b/tests/test_te_manager.py @@ -5,7 +5,7 @@ import networkx as nx from sdx_pce.load_balancing.te_solver import TESolver -from sdx_pce.models import ConnectionRequest, ConnectionSolution, TrafficMatrix +from sdx_pce.models import ConnectionSolution, PceConnectionRequest, TrafficMatrix from sdx_pce.topology.temanager import TEManager from sdx_pce.utils.exceptions import ( RequestValidationError, @@ -113,7 +113,7 @@ def test_find_common_vlan_on_link(self): def test_generate_solver_input(self): print("Test Convert Connection To Topology") - request = json.loads(TestData.CONNECTION_REQ_AMLIGHT.read_text()) + request = json.loads(TestData.CONNECTION_REQ_AMLIGHT_v0.read_text()) temanager = TEManager( topology_data=json.loads(TestData.TOPOLOGY_FILE_AMLIGHT.read_text()) @@ -334,7 +334,7 @@ def test_generate_graph_and_connection_with_sax_2_invalid(self): # Expect None because the connection_data contains # unresolvable port IDs, which are not present in the given # topology. - request = json.loads(TestData.CONNECTION_REQ_FILE_SAX_2_INVALID.read_text()) + request = json.loads(TestData.CONNECTION_REQ_FILE_SAX_2_INVALID_v0.read_text()) tm = None with self.assertRaises(RequestValidationError) as ctx: tm = temanager.generate_traffic_matrix(request) @@ -359,7 +359,7 @@ def test_generate_graph_and_connection_with_sax_2_valid(self): self.assertIsNotNone(graph) self.assertIsInstance(graph, nx.Graph) - request = json.loads(TestData.CONNECTION_REQ_FILE_SAX_2_VALID.read_text()) + request = json.loads(TestData.CONNECTION_REQ_FILE_SAX_2_VALID_v0.read_text()) tm = temanager.generate_traffic_matrix(request) print(f"traffic matrix: {tm}") self.assertIsInstance(tm, TrafficMatrix) @@ -393,7 +393,7 @@ def test_connection_amlight(self): self.assertIsInstance(graph, nx.Graph) - request = json.loads(TestData.CONNECTION_REQ_AMLIGHT.read_text()) + request = json.loads(TestData.CONNECTION_REQ_AMLIGHT_v0.read_text()) print(f"connection request: {request}") traffic_matrix = temanager.generate_traffic_matrix(request) @@ -515,7 +515,7 @@ def test_connection_amlight_user_port(self): self.assertIsInstance(graph, nx.Graph) connection_request = json.loads( - TestData.CONNECTION_REQ_AMLIGHT_USER_PORT.read_text() + TestData.CONNECTION_REQ_AMLIGHT_USER_PORT_v0.read_text() ) print(f"connection request: {connection_request}") @@ -555,7 +555,7 @@ def test_connection_amlight_to_zaoxi(self): graph = temanager.generate_graph_te() - connection_request = json.loads(TestData.CONNECTION_REQ.read_text()) + connection_request = json.loads(TestData.CONNECTION_REQ_v0.read_text()) print(f"connection_request: {connection_request}") traffic_matrix = temanager.generate_traffic_matrix(connection_request) @@ -653,7 +653,7 @@ def test_connection_amlight_to_zaoxi_user_port(self): graph = temanager.generate_graph_te() connection_request = json.loads( - TestData.CONNECTION_REQ_AMLIGHT_ZAOXI_USER_PORT.read_text() + TestData.CONNECTION_REQ_AMLIGHT_ZAOXI_USER_PORT_v0.read_text() ) print(f"connection_request: {connection_request}") traffic_matrix = temanager.generate_traffic_matrix(connection_request) @@ -748,7 +748,7 @@ def test_delete_connection(self): # Create a connection request connection_request = json.loads( - TestData.CONNECTION_REQ_AMLIGHT_SAX_v2.read_text() + TestData.CONNECTION_REQ_AMLIGHT_SAX_v1.read_text() ) traffic_matrix = temanager.generate_traffic_matrix(connection_request) self.assertIsInstance(traffic_matrix, TrafficMatrix) @@ -788,7 +788,7 @@ def test_connection_amlight_to_sax_v2(self): graph = temanager.generate_graph_te() connection_request = json.loads( - TestData.CONNECTION_REQ_AMLIGHT_SAX_v2.read_text() + TestData.CONNECTION_REQ_AMLIGHT_SAX_v1.read_text() ) print(f"connection_request: {connection_request}") traffic_matrix = temanager.generate_traffic_matrix(connection_request) @@ -860,7 +860,7 @@ def test_connection_amlight_to_zaoxi_two_identical_requests(self): graph = temanager.generate_graph_te() - connection_request = json.loads(TestData.CONNECTION_REQ.read_text()) + connection_request = json.loads(TestData.CONNECTION_REQ_v0.read_text()) print(f"connection_request: {connection_request}") traffic_matrix = temanager.generate_traffic_matrix(connection_request) @@ -935,7 +935,7 @@ def test_connection_amlight_to_zaoxi_many_identical_requests(self): graph = temanager.generate_graph_te() - connection_request = json.loads(TestData.CONNECTION_REQ.read_text()) + connection_request = json.loads(TestData.CONNECTION_REQ_v0.read_text()) init_vlan = connection_request["ingress_port"]["label_range"] breakdowns = set() num_requests = 10 @@ -999,7 +999,7 @@ def test_connection_amlight_to_zaoxi_two_distinct_requests(self): self.assertIsInstance(graph, nx.Graph) # Use a connection request that should span all three domains. - connection_request1 = json.loads(TestData.CONNECTION_REQ.read_text()) + connection_request1 = json.loads(TestData.CONNECTION_REQ_v0.read_text()) print(f"Connection request #1: {connection_request1}") traffic_matrix1 = temanager.generate_traffic_matrix(connection_request1) @@ -1020,7 +1020,7 @@ def test_connection_amlight_to_zaoxi_two_distinct_requests(self): graph = TESolver(graph, traffic_matrix1).update_graph(graph, solution1) # Use another connection request that spans just one domain. - connection_request2 = json.loads(TestData.CONNECTION_REQ_AMLIGHT.read_text()) + connection_request2 = json.loads(TestData.CONNECTION_REQ_AMLIGHT_v0.read_text()) print(f"Connection request #2: {connection_request2}") traffic_matrix2 = temanager.generate_traffic_matrix(connection_request2) @@ -1072,7 +1072,7 @@ def test_connection_amlight_to_zaoxi_two_distinct_requests_concurrent(self): # Step 2: connections connection_object_map = {} # Use a connection request that should span all three domains. - connection_request1 = json.loads(TestData.CONNECTION_REQ.read_text()) + connection_request1 = json.loads(TestData.CONNECTION_REQ_v0.read_text()) print(f"Connection request #1: {connection_request1}") traffic_matrix = temanager.generate_traffic_matrix(connection_request1) @@ -1083,7 +1083,7 @@ def test_connection_amlight_to_zaoxi_two_distinct_requests_concurrent(self): ) # Use another connection request that spans just one domain. - connection_request2 = json.loads(TestData.CONNECTION_REQ_AMLIGHT.read_text()) + connection_request2 = json.loads(TestData.CONNECTION_REQ_AMLIGHT_v0.read_text()) print(f"Connection request #2: {connection_request2}") traffic_matrix2 = temanager.generate_traffic_matrix(connection_request2) @@ -1137,7 +1137,7 @@ def test_connection_amlight_to_zaoxi_unreserve(self): graph = temanager.generate_graph_te() - connection_request = json.loads(TestData.CONNECTION_REQ.read_text()) + connection_request = json.loads(TestData.CONNECTION_REQ_v0.read_text()) print(f"connection_request: {connection_request}") traffic_matrix = temanager.generate_traffic_matrix(connection_request) @@ -1198,7 +1198,7 @@ def test_connection_amlight_to_zaoxi_with_merged_topology(self): graph = temanager.generate_graph_te() - connection_request = json.loads(TestData.CONNECTION_REQ.read_text()) + connection_request = json.loads(TestData.CONNECTION_REQ_v0.read_text()) print(f"connection_request: {connection_request}") traffic_matrix = temanager.generate_traffic_matrix(connection_request) @@ -1236,7 +1236,7 @@ def test_generate_graph_and_connection(self): self.assertIsNotNone(graph) self.assertIsInstance(graph, nx.Graph) - request = json.loads(TestData.CONNECTION_REQ_AMLIGHT.read_text()) + request = json.loads(TestData.CONNECTION_REQ_AMLIGHT_v0.read_text()) tm = temanager.generate_traffic_matrix(request) print(f"tm: {tm}") @@ -1307,7 +1307,7 @@ def _make_traffic_matrix_from_list(self, old_style_request: list) -> TrafficMatr cost = old_style_request[1] print(f"cost: {cost}") - new_requests: list(ConnectionRequest) = [] + new_requests: list(PceConnectionRequest) = [] print(f"type of request: {type(requests_map)}") assert isinstance(requests_map, dict) @@ -1337,7 +1337,7 @@ def _make_traffic_matrix_from_list(self, old_style_request: list) -> TrafficMatr assert len(request) == 2 new_requests.append( - ConnectionRequest( + PceConnectionRequest( source=source, destination=destination, required_bandwidth=required_bandwidth, @@ -1364,18 +1364,9 @@ def test_connection_amlight_to_zaoxi_user_port_v2(self): graph = temanager.generate_graph_te() connection_request = json.loads( - TestData.CONNECTION_REQ_AMLIGHT_ZAOXI_USER_PORT_v2.read_text() + TestData.CONNECTION_REQ_AMLIGHT_ZAOXI_USER_PORT_v1.read_text() ) - # Modify the connection request for this test so that we have - # a solvable one. The original one asks for (1) a VLAN that is - # not present on the ingress port (777), and (2) a range - # ("55:90") on the egress port. This is an unsolvable request - # because of (1), and an invalid one because of (2) since both - # ports have to use a range. - connection_request["endpoints"][0]["vlan"] = "100" - connection_request["endpoints"][1]["vlan"] = "100" - print(f"connection_request: {connection_request}") traffic_matrix = temanager.generate_traffic_matrix(connection_request) @@ -1489,7 +1480,7 @@ def test_connection_amlight_to_zaoxi_user_port_any(self): graph = temanager.generate_graph_te() connection_request = json.loads( - TestData.CONNECTION_REQ_AMLIGHT_ZAOXI_USER_PORT_v2.read_text() + TestData.CONNECTION_REQ_AMLIGHT_ZAOXI_USER_PORT_v1.read_text() ) # Rewrite the request to have VLAN of "any". @@ -1583,7 +1574,7 @@ def test_identical_vlan_ranges(self): }, { "port_id": "urn:sdx:port:amlight:B1:1", - "vlan": "55:90" + "vlan": "777" } ] } @@ -1607,7 +1598,7 @@ def test_identical_vlan_ranges(self): print(f"ex = {ex}") self.assertIsNotNone(graph) - self.assertIsNone(traffic_matrix) + self.assertIsNotNone(traffic_matrix) def test_disallowed_vlan(self): """ diff --git a/tests/test_te_solver_static.py b/tests/test_te_solver_static.py index 553e4562..9eb7af09 100644 --- a/tests/test_te_solver_static.py +++ b/tests/test_te_solver_static.py @@ -33,7 +33,7 @@ def setUp(self): topology_data = json.loads(TestData.TOPOLOGY_FILE_SDX.read_text()) self.temanager = TEManager(topology_data) - self.connection_request = json.loads(TestData.CONNECTION_REQ.read_text()) + self.connection_request = json.loads(TestData.CONNECTION_REQ_v0.read_text()) def test_computation_breakdown(self): graph = self.temanager.generate_graph_te() diff --git a/tox.ini b/tox.ini index 3ecdb2e2..2ebbeac4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,5 @@ [tox] env_list = - py39 - py310 py311 py312