From 2ea593636c6bb48a6a5e9730efe3689775fcc064 Mon Sep 17 00:00:00 2001 From: armingol Date: Tue, 19 May 2026 15:37:53 +0000 Subject: [PATCH 01/12] feat: add OpticalPathComp support for P2MP connectivity services and integrate into E2E orchestrator --- src/e2e_orchestrator/Dockerfile | 3 + .../E2EOrchestratorServiceServicerImpl.py | 15 + .../frontend/service/OpticalPathComp.py | 422 ++++++++++++++++++ .../service/PathCompServiceServicerImpl.py | 7 + 4 files changed, 447 insertions(+) create mode 100644 src/pathcomp/frontend/service/OpticalPathComp.py diff --git a/src/e2e_orchestrator/Dockerfile b/src/e2e_orchestrator/Dockerfile index 847e75ed4..3751c02a8 100644 --- a/src/e2e_orchestrator/Dockerfile +++ b/src/e2e_orchestrator/Dockerfile @@ -82,6 +82,9 @@ COPY src/context/client/. context/client/ COPY src/context/service/database/uuids/. context/service/database/uuids/ COPY src/service/__init__.py service/__init__.py COPY src/service/client/. service/client/ +COPY src/pathcomp/__init__.py pathcomp/__init__.py +COPY src/pathcomp/frontend/__init__.py pathcomp/frontend/__init__.py +COPY src/pathcomp/frontend/client/. pathcomp/frontend/client/ COPY src/e2e_orchestrator/. e2e_orchestrator/ # Start the service diff --git a/src/e2e_orchestrator/service/E2EOrchestratorServiceServicerImpl.py b/src/e2e_orchestrator/service/E2EOrchestratorServiceServicerImpl.py index ead3aeb83..7e8732787 100644 --- a/src/e2e_orchestrator/service/E2EOrchestratorServiceServicerImpl.py +++ b/src/e2e_orchestrator/service/E2EOrchestratorServiceServicerImpl.py @@ -19,6 +19,9 @@ from common.proto.context_pb2 import Empty, Connection, EndPointId from common.proto.e2eorchestrator_pb2_grpc import E2EOrchestratorServiceServicer from context.client.ContextClient import ContextClient from context.service.database.uuids.EndPoint import endpoint_get_uuid +from common.proto.context_pb2 import ServiceTypeEnum +from pathcomp.frontend.client.PathCompClient import PathCompClient +from common.proto.pathcomp_pb2 import PathCompRequest LOGGER = logging.getLogger(__name__) @@ -33,6 +36,18 @@ class E2EOrchestratorServiceServicerImpl(E2EOrchestratorServiceServicer): def Compute( self, request: E2EOrchestratorRequest, context: grpc.ServicerContext ) -> E2EOrchestratorReply: + if request.service.service_type == ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY: + LOGGER.info("E2E Orchestrator: Detected OPTICAL_CONNECTIVITY service. Calling PathComp.") + pathcomp_client = PathCompClient() + pathcomp_req = PathCompRequest() + pathcomp_req.services.append(request.service) + pathcomp_reply = pathcomp_client.Compute(pathcomp_req) + + e2e_reply = E2EOrchestratorReply() + e2e_reply.services.extend(pathcomp_reply.services) + e2e_reply.connections.extend(pathcomp_reply.connections) + return e2e_reply + endpoints_ids = [ endpoint_get_uuid(endpoint_id)[2] for endpoint_id in request.service.service_endpoint_ids diff --git a/src/pathcomp/frontend/service/OpticalPathComp.py b/src/pathcomp/frontend/service/OpticalPathComp.py new file mode 100644 index 000000000..43808d9db --- /dev/null +++ b/src/pathcomp/frontend/service/OpticalPathComp.py @@ -0,0 +1,422 @@ +import json +import requests +import uuid +from common.proto.context_pb2 import Service, ConfigRule, ConfigActionEnum +from common.proto.pathcomp_pb2 import PathCompReply + + +def safe_get(d, keys, default=None): + for key in keys: + if isinstance(d, dict): + d = d.get(key, default) + else: + return default + return d + +def group_block(group, action, group_id_override=None, node=None): + active = "true" if action == 'create' else "false" + group_id = group_id_override if group_id_override is not None else group.get("group-id", group.get("digital_sub_carriers_group_id", 1)) + + if node == "leaf": + return { + "digital_sub_carriers_group_id": group_id, + "digital_sub_carrier_id": [ + {'sub_carrier_id': 1, 'active': active}, + {'sub_carrier_id': 2, 'active': active}, + {'sub_carrier_id': 3, 'active': active}, + {'sub_carrier_id': 4, 'active': active} + ] + } + else: + return { + "digital_sub_carriers_group_id": group_id, + "digital_sub_carrier_id": [ + { + "sub_carrier_id": sid, + "active": active, + } + for sid in group.get("subcarrier-id", []) + ] + } + +def compute_optical_path(service: Service) -> PathCompReply: + # Extract intent from config rules + intent_str = "" + for cr in service.service_config.config_rules: + if cr.WhichOneof('config_rule') == 'custom' and cr.custom.resource_key == "intent": + intent_str = cr.custom.resource_value + break + + intent = json.loads(intent_str) if intent_str else {} + action = "create" # Default action + + # Extract src (sender) and dst (receivers) from intent + services = intent.get("ietf-network-slice-service:network-slice-services", {}).get("slice-service", []) + source = None + destination = None + + for srv in services: + c_groups = srv.get("connection-groups", {}).get("connection-group", []) + for cg in c_groups: + constructs = cg.get("connectivity-construct", []) + for construct in constructs: + source = construct.get("p2mp-sender-sdp") + destination = construct.get("p2mp-receiver-sdp") + if source and destination: + break + if source and destination: + break + if source and destination: + break + + if not source or not destination: + raise Exception("Missing p2mp-sender-sdp or p2mp-receiver-sdp parameters in the intent") + + if isinstance(source, str): + sources_list = [source] + else: + sources_list = list(source) + + if isinstance(destination, str): + destinations_list = [destination] + else: + destinations_list = list(destination) + + # In optical networks, the leaves are often the "sources" of light for the computation or vice-versa. + # Based on user's instruction: sources = receivers, destinations = sender + payload = { + "sources": destinations_list, + "destinations": sources_list, + "bitrate": 100, + "bidirectional": True, + "band": 200, + "subcarriers_per_source": [4] * len(destinations_list) + } + + url = "http://10.30.7.66:31060/OpticalTFS/restconf/operations/tapi-path-computation:compute-p2mp" + headers = { + "Content-Type": "application/json", + "Accept": "*/*" + } + + resp = requests.post(url, headers=headers, json=payload, timeout=15) + resp.raise_for_status() + resp_json = resp.json() + + # MOCK RESPONSE (If the Optical Controller is down) + # resp_json = { + # "tapi-connectivity:connectivity-service": { + # "connection": [ + # { + # "optical-connection-attributes": { + # "central-frequency": 195000000000000, + # "Tx-power": 0, + # "modulation": { + # "modulation-technique": "DP-16QAM", + # "operational-mode": 9, + # "port": "port-1" + # }, + # "digital-subcarrier-spacing": 300000000, + # "subcarrier-attributes": { + # "digital-subcarrier-group": [ + # { + # "group-id": 1, + # "modulation-technique": "DP-QPSK", + # "central-frequency": 195006250, + # "operational-mode": 4, + # "Tx-power": -99, + # "group-size": 4, + # "port": "port-1", + # "subcarrier-id": [1, 2, 3, 4] + # }, + # { + # "group-id": 2, + # "modulation-technique": "DP-QPSK", + # "central-frequency": 195018750, + # "operational-mode": 4, + # "Tx-power": -99, + # "group-size": 4, + # "port": "port-3", + # "subcarrier-id": [5, 6, 7, 8] + # }, + # { + # "group-id": 3, + # "modulation-technique": "DP-QPSK", + # "central-frequency": 195031250, + # "operational-mode": 4, + # "Tx-power": -99, + # "group-size": 4, + # "port": "port-5", + # "subcarrier-id": [9, 10, 11, 12] + # } + # ] + # } + # }, + # "end-point": [ + # { + # "direction": "BIDIRECTIONAL", + # "layer-protocol-name": "PHOTONIC_MEDIA", + # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + # "local-id": "T1.1", + # "service-interface-point": { + # "service-interface-point-uuid": "T2.1" + # }, + # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + # "mc-config": { + # "spectrum": { + # "center-frequency": 195000000000000, + # "frequency-constraint": { + # "adjustment-granularity": "G_6_25GHZ", + # "grid-type": "FLEX" + # } + # } + # } + # } + # }, + # { + # "direction": "BIDIRECTIONAL", + # "layer-protocol-name": "PHOTONIC_MEDIA", + # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + # "local-id": "T2.1", + # "service-interface-point": { + # "service-interface-point-uuid": "T1.1" + # }, + # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + # "mc-config": { + # "spectrum": { + # "center-frequency": 195006250, + # "frequency-constraint": { + # "adjustment-granularity": "G_6_25GHZ", + # "grid-type": "FLEX" + # } + # } + # } + # } + # }, + # { + # "direction": "BIDIRECTIONAL", + # "layer-protocol-name": "PHOTONIC_MEDIA", + # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + # "local-id": "T1.2", + # "service-interface-point": { + # "service-interface-point-uuid": "T2.1" + # }, + # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + # "mc-config": { + # "spectrum": { + # "center-frequency": 195018750, + # "frequency-constraint": { + # "adjustment-granularity": "G_6_25GHZ", + # "grid-type": "FLEX" + # } + # } + # } + # } + # }, + # { + # "direction": "BIDIRECTIONAL", + # "layer-protocol-name": "PHOTONIC_MEDIA", + # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + # "local-id": "T2.1", + # "service-interface-point": { + # "service-interface-point-uuid": "T1.2" + # }, + # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + # "mc-config": { + # "spectrum": { + # "center-frequency": 195018750, + # "frequency-constraint": { + # "adjustment-granularity": "G_6_25GHZ", + # "grid-type": "FLEX" + # } + # } + # } + # } + # }, + # { + # "direction": "BIDIRECTIONAL", + # "layer-protocol-name": "PHOTONIC_MEDIA", + # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + # "local-id": "T1.3", + # "service-interface-point": { + # "service-interface-point-uuid": "T2.1" + # }, + # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + # "mc-config": { + # "spectrum": { + # "center-frequency": 195031250, + # "frequency-constraint": { + # "adjustment-granularity": "G_6_25GHZ", + # "grid-type": "FLEX" + # } + # } + # } + # } + # }, + # { + # "direction": "BIDIRECTIONAL", + # "layer-protocol-name": "PHOTONIC_MEDIA", + # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + # "local-id": "T2.1", + # "service-interface-point": { + # "service-interface-point-uuid": "T1.3" + # }, + # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + # "mc-config": { + # "spectrum": { + # "center-frequency": 195031250, + # "frequency-constraint": { + # "adjustment-granularity": "G_6_25GHZ", + # "grid-type": "FLEX" + # } + # } + # } + # } + # } + # ] + # } + # ] + # } + # } + + # Generate Rules + src_name = source # T2.1 + dest_list = destinations_list # T1.1, T1.2 + # Extract destinations from response end-points if available + try: + endpoints = resp_json.get("tapi-connectivity:connectivity-service", {}).get("connection", [{}])[0].get("end-point", []) + extracted_dests = [] + for ep in endpoints: + local_id = ep.get("local-id") + if local_id and local_id != src_name and local_id not in extracted_dests: + extracted_dests.append(local_id) + if extracted_dests: + dest_list = extracted_dests + except Exception: + pass + + dest_str = ",".join(dest_list) + config_rules = [] + + network_slice_uuid_str = f"{src_name}_to_{dest_str}" + tunnel_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, network_slice_uuid_str)) + + provisionamiento = { + "network-slice-uuid": network_slice_uuid_str, + "viability": True, + "actions": [] + } + + try: + attributes = resp_json.get("tapi-connectivity:connectivity-service", {}).get("connection", [{}])[0].get("optical-connection-attributes", {}) + groups = attributes.get("subcarrier-attributes", {}).get("digital-subcarrier-group", []) + operational_mode = attributes.get("modulation", {}).get("operational-mode", 9) + except Exception: + # Provide fallback if mock doesn't match perfectly + groups = resp_json.get("digital-subcarrier-groups", []) + operational_mode = resp_json.get("op-mode", 9) + + hub_groups = [ + group_block(group, action, group_id_override=index + 1) + for index, group in enumerate(groups) + ] + + hub_freq_raw = attributes.get("central-frequency", 195000000000000) + hub_freq = int(hub_freq_raw / 1e6) if hub_freq_raw > 1e10 else int(hub_freq_raw) + + hub = { + "name": "channel-1", + "frequency": hub_freq, + "target_output_power": attributes.get("Tx-power", 0), + "operational_mode": operational_mode, + "operation": "merge", + "digital_sub_carriers_group": hub_groups + } + + leaves = [] + print("dest_list:", dest_list) + print("groups:", groups) + for dest, group in zip(dest_list, groups): + port = group.get("port", "port-1") + if port.startswith("port-"): + name = f"channel-{port.split('-')[1]}" + else: + name = "channel-1" + + freq_raw = group.get("central-frequency", 195006250) + freq = int(freq_raw / 1e6) if freq_raw > 1e10 else int(freq_raw) + + leaf = { + "name": name, + "frequency": freq, + "target_output_power": group.get("Tx-power", 0), + "operational_mode": int(group.get("operational-mode", operational_mode)), + "operation": "merge", + "digital_sub_carriers_group": [group_block(group, action, group_id_override=1, node="leaf")] + } + leaves.append(leaf) + + final_json = {"components": [hub] + leaves} + + # Add transceiver activation action + provisionamiento["actions"].append({ + "type": "XR_AGENT_ACTIVATE_TRANSCEIVER", + "layer": "OPTICAL", + "content": final_json, + "controller-uuid": "IPoWDM Controller" + }) + + # Extract IP configuration from intent for L3 VPN setup + nodes = {} + sdp_list = intent.get('ietf-network-slice-service:network-slice-services', {}).get('slice-service', [{}])[0].get('sdps', {}).get('sdp', []) + + for sdp in sdp_list: + node = sdp.get('id') + attachments = sdp.get('attachment-circuits', {}).get('attachment-circuit', []) + for ac in attachments: + ip = ac.get('ac-ipv4-address', None) + prefix = ac.get('ac-ipv4-prefix-length', None) + vlan = 500 # Fixed VLAN ID + nodes[node] = { + "ip-address": ip, + "ip-mask": prefix, + "vlan-id": vlan + } + + # Add L3 VPN configuration action for P2MP topology + if src_name in nodes: + content = { + "tunnel-uuid": tunnel_uuid, + "src-node-uuid": src_name, + "src-ip-address": nodes[src_name]["ip-address"], + "src-ip-mask": str(nodes[src_name]["ip-mask"]), + "src-vlan-id": nodes[src_name]["vlan-id"], + } + for i, dest in enumerate(dest_list): + if dest in nodes: + content[f"dest{i+1}-node-uuid"] = dest + content[f"dest{i+1}-ip-address"] = nodes[dest]["ip-address"] + content[f"dest{i+1}-ip-mask"] = str(nodes[dest]["ip-mask"]) + content[f"dest{i+1}-vlan-id"] = nodes[dest]["vlan-id"] + + provisionamiento["actions"].append({ + "type": "CONFIG_VPNL3", + "layer": "IP", + "content": content, + "controller-uuid": "IP Controller" + }) + + config_rules.append(provisionamiento) + + reply = PathCompReply() + reply_svc = Service() + reply_svc.CopyFrom(service) + + cr = ConfigRule() + cr.action = ConfigActionEnum.CONFIGACTION_SET + cr.custom.resource_key = "optical_path_result" + cr.custom.resource_value = json.dumps(config_rules) + reply_svc.service_config.config_rules.append(cr) + reply.services.append(reply_svc) + + return reply diff --git a/src/pathcomp/frontend/service/PathCompServiceServicerImpl.py b/src/pathcomp/frontend/service/PathCompServiceServicerImpl.py index 4e832c258..d7e3f2daf 100644 --- a/src/pathcomp/frontend/service/PathCompServiceServicerImpl.py +++ b/src/pathcomp/frontend/service/PathCompServiceServicerImpl.py @@ -28,6 +28,8 @@ from pathcomp.frontend.Config import is_forecaster_enabled #from context.client.ContextClient import ContextClient from pathcomp.frontend.service.TopologyTools import get_pathcomp_topology_details from pathcomp.frontend.service.algorithms.Factory import get_algorithm +from pathcomp.frontend.service.OpticalPathComp import compute_optical_path +from common.proto.context_pb2 import ServiceTypeEnum LOGGER = logging.getLogger(__name__) @@ -45,6 +47,11 @@ class PathCompServiceServicerImpl(PathCompServiceServicer): def Compute(self, request : PathCompRequest, context : grpc.ServicerContext) -> PathCompReply: LOGGER.debug('[Compute] begin ; request = {:s}'.format(grpc_message_to_json_string(request))) + if len(request.services) > 0 and request.services[0].service_type == ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY: + LOGGER.info('[Compute] Intercepting OPTICAL_CONNECTIVITY request...') + return compute_optical_path(request.services[0]) + + #context_client = ContextClient() # TODO: improve definition of topologies; for interdomain the current topology design might be not convenient #if (len(request.services) == 1) and is_inter_domain(context_client, request.services[0].service_endpoint_ids): -- GitLab From 7ec87bb3b60cc818ac7eb95cf714ca0c2bf3a045 Mon Sep 17 00:00:00 2001 From: armingol Date: Tue, 26 May 2026 08:07:58 +0000 Subject: [PATCH 02/12] feat: implement extract_teraflowsdn_device_name function and integrate device name into optical path computation --- .../frontend/service/OpticalPathComp.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/pathcomp/frontend/service/OpticalPathComp.py b/src/pathcomp/frontend/service/OpticalPathComp.py index 43808d9db..e8754f073 100644 --- a/src/pathcomp/frontend/service/OpticalPathComp.py +++ b/src/pathcomp/frontend/service/OpticalPathComp.py @@ -13,6 +13,38 @@ def safe_get(d, keys, default=None): return default return d + +def extract_teraflowsdn_device_name(service: Service): + for cr in service.service_config.config_rules: + if cr.WhichOneof('config_rule') != 'custom': + continue + + raw_value = cr.custom.resource_value + if not raw_value: + continue + + try: + data = json.loads(raw_value) + except Exception: + continue + + if not isinstance(data, dict): + continue + + device_type = data.get('device_type') or data.get('type') + if device_type != 'teraflowsdn': + continue + + drivers = data.get('device_drivers') + if not isinstance(drivers, list): + continue + + if 'DEVICEDRIVER_IETF_L3VPN' in drivers: + return data.get('device_name') or data.get('name') + + return None + + def group_block(group, action, group_id_override=None, node=None): active = "true" if action == 'create' else "false" group_id = group_id_override if group_id_override is not None else group.get("group-id", group.get("digital_sub_carriers_group_id", 1)) @@ -301,11 +333,14 @@ def compute_optical_path(service: Service) -> PathCompReply: network_slice_uuid_str = f"{src_name}_to_{dest_str}" tunnel_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, network_slice_uuid_str)) + device_name = extract_teraflowsdn_device_name(service) provisionamiento = { "network-slice-uuid": network_slice_uuid_str, "viability": True, "actions": [] } + if device_name: + provisionamiento["device_name"] = device_name try: attributes = resp_json.get("tapi-connectivity:connectivity-service", {}).get("connection", [{}])[0].get("optical-connection-attributes", {}) @@ -392,6 +427,8 @@ def compute_optical_path(service: Service) -> PathCompReply: "src-ip-mask": str(nodes[src_name]["ip-mask"]), "src-vlan-id": nodes[src_name]["vlan-id"], } + if device_name: + content["device_name"] = device_name for i, dest in enumerate(dest_list): if dest in nodes: content[f"dest{i+1}-node-uuid"] = dest -- GitLab From 35eb3a955a85614b4a81cf99a797d2b38d38b3cb Mon Sep 17 00:00:00 2001 From: armingol Date: Tue, 26 May 2026 15:47:59 +0000 Subject: [PATCH 03/12] feat: remove extract_teraflowsdn_device_name function and update compute_optical_path with mock response --- .../frontend/service/OpticalPathComp.py | 391 ++++++++---------- 1 file changed, 179 insertions(+), 212 deletions(-) diff --git a/src/pathcomp/frontend/service/OpticalPathComp.py b/src/pathcomp/frontend/service/OpticalPathComp.py index e8754f073..eaee91c52 100644 --- a/src/pathcomp/frontend/service/OpticalPathComp.py +++ b/src/pathcomp/frontend/service/OpticalPathComp.py @@ -5,6 +5,8 @@ from common.proto.context_pb2 import Service, ConfigRule, ConfigActionEnum from common.proto.pathcomp_pb2 import PathCompReply +LOGGER = logging.getLogger(__name__) + def safe_get(d, keys, default=None): for key in keys: if isinstance(d, dict): @@ -14,37 +16,6 @@ def safe_get(d, keys, default=None): return d -def extract_teraflowsdn_device_name(service: Service): - for cr in service.service_config.config_rules: - if cr.WhichOneof('config_rule') != 'custom': - continue - - raw_value = cr.custom.resource_value - if not raw_value: - continue - - try: - data = json.loads(raw_value) - except Exception: - continue - - if not isinstance(data, dict): - continue - - device_type = data.get('device_type') or data.get('type') - if device_type != 'teraflowsdn': - continue - - drivers = data.get('device_drivers') - if not isinstance(drivers, list): - continue - - if 'DEVICEDRIVER_IETF_L3VPN' in drivers: - return data.get('device_name') or data.get('name') - - return None - - def group_block(group, action, group_id_override=None, node=None): active = "true" if action == 'create' else "false" group_id = group_id_override if group_id_override is not None else group.get("group-id", group.get("digital_sub_carriers_group_id", 1)) @@ -131,185 +102,185 @@ def compute_optical_path(service: Service) -> PathCompReply: "Accept": "*/*" } - resp = requests.post(url, headers=headers, json=payload, timeout=15) - resp.raise_for_status() - resp_json = resp.json() + # resp = requests.post(url, headers=headers, json=payload, timeout=15) + # resp.raise_for_status() + # resp_json = resp.json() # MOCK RESPONSE (If the Optical Controller is down) - # resp_json = { - # "tapi-connectivity:connectivity-service": { - # "connection": [ - # { - # "optical-connection-attributes": { - # "central-frequency": 195000000000000, - # "Tx-power": 0, - # "modulation": { - # "modulation-technique": "DP-16QAM", - # "operational-mode": 9, - # "port": "port-1" - # }, - # "digital-subcarrier-spacing": 300000000, - # "subcarrier-attributes": { - # "digital-subcarrier-group": [ - # { - # "group-id": 1, - # "modulation-technique": "DP-QPSK", - # "central-frequency": 195006250, - # "operational-mode": 4, - # "Tx-power": -99, - # "group-size": 4, - # "port": "port-1", - # "subcarrier-id": [1, 2, 3, 4] - # }, - # { - # "group-id": 2, - # "modulation-technique": "DP-QPSK", - # "central-frequency": 195018750, - # "operational-mode": 4, - # "Tx-power": -99, - # "group-size": 4, - # "port": "port-3", - # "subcarrier-id": [5, 6, 7, 8] - # }, - # { - # "group-id": 3, - # "modulation-technique": "DP-QPSK", - # "central-frequency": 195031250, - # "operational-mode": 4, - # "Tx-power": -99, - # "group-size": 4, - # "port": "port-5", - # "subcarrier-id": [9, 10, 11, 12] - # } - # ] - # } - # }, - # "end-point": [ - # { - # "direction": "BIDIRECTIONAL", - # "layer-protocol-name": "PHOTONIC_MEDIA", - # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", - # "local-id": "T1.1", - # "service-interface-point": { - # "service-interface-point-uuid": "T2.1" - # }, - # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { - # "mc-config": { - # "spectrum": { - # "center-frequency": 195000000000000, - # "frequency-constraint": { - # "adjustment-granularity": "G_6_25GHZ", - # "grid-type": "FLEX" - # } - # } - # } - # } - # }, - # { - # "direction": "BIDIRECTIONAL", - # "layer-protocol-name": "PHOTONIC_MEDIA", - # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", - # "local-id": "T2.1", - # "service-interface-point": { - # "service-interface-point-uuid": "T1.1" - # }, - # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { - # "mc-config": { - # "spectrum": { - # "center-frequency": 195006250, - # "frequency-constraint": { - # "adjustment-granularity": "G_6_25GHZ", - # "grid-type": "FLEX" - # } - # } - # } - # } - # }, - # { - # "direction": "BIDIRECTIONAL", - # "layer-protocol-name": "PHOTONIC_MEDIA", - # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", - # "local-id": "T1.2", - # "service-interface-point": { - # "service-interface-point-uuid": "T2.1" - # }, - # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { - # "mc-config": { - # "spectrum": { - # "center-frequency": 195018750, - # "frequency-constraint": { - # "adjustment-granularity": "G_6_25GHZ", - # "grid-type": "FLEX" - # } - # } - # } - # } - # }, - # { - # "direction": "BIDIRECTIONAL", - # "layer-protocol-name": "PHOTONIC_MEDIA", - # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", - # "local-id": "T2.1", - # "service-interface-point": { - # "service-interface-point-uuid": "T1.2" - # }, - # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { - # "mc-config": { - # "spectrum": { - # "center-frequency": 195018750, - # "frequency-constraint": { - # "adjustment-granularity": "G_6_25GHZ", - # "grid-type": "FLEX" - # } - # } - # } - # } - # }, - # { - # "direction": "BIDIRECTIONAL", - # "layer-protocol-name": "PHOTONIC_MEDIA", - # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", - # "local-id": "T1.3", - # "service-interface-point": { - # "service-interface-point-uuid": "T2.1" - # }, - # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { - # "mc-config": { - # "spectrum": { - # "center-frequency": 195031250, - # "frequency-constraint": { - # "adjustment-granularity": "G_6_25GHZ", - # "grid-type": "FLEX" - # } - # } - # } - # } - # }, - # { - # "direction": "BIDIRECTIONAL", - # "layer-protocol-name": "PHOTONIC_MEDIA", - # "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", - # "local-id": "T2.1", - # "service-interface-point": { - # "service-interface-point-uuid": "T1.3" - # }, - # "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { - # "mc-config": { - # "spectrum": { - # "center-frequency": 195031250, - # "frequency-constraint": { - # "adjustment-granularity": "G_6_25GHZ", - # "grid-type": "FLEX" - # } - # } - # } - # } - # } - # ] - # } - # ] - # } - # } + resp_json = { + "tapi-connectivity:connectivity-service": { + "connection": [ + { + "optical-connection-attributes": { + "central-frequency": 195000000000000, + "Tx-power": 0, + "modulation": { + "modulation-technique": "DP-16QAM", + "operational-mode": 9, + "port": "port-1" + }, + "digital-subcarrier-spacing": 300000000, + "subcarrier-attributes": { + "digital-subcarrier-group": [ + { + "group-id": 1, + "modulation-technique": "DP-QPSK", + "central-frequency": 195006250, + "operational-mode": 4, + "Tx-power": -99, + "group-size": 4, + "port": "port-1", + "subcarrier-id": [1, 2, 3, 4] + }, + { + "group-id": 2, + "modulation-technique": "DP-QPSK", + "central-frequency": 195018750, + "operational-mode": 4, + "Tx-power": -99, + "group-size": 4, + "port": "port-3", + "subcarrier-id": [5, 6, 7, 8] + }, + { + "group-id": 3, + "modulation-technique": "DP-QPSK", + "central-frequency": 195031250, + "operational-mode": 4, + "Tx-power": -99, + "group-size": 4, + "port": "port-5", + "subcarrier-id": [9, 10, 11, 12] + } + ] + } + }, + "end-point": [ + { + "direction": "BIDIRECTIONAL", + "layer-protocol-name": "PHOTONIC_MEDIA", + "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + "local-id": "T1.1", + "service-interface-point": { + "service-interface-point-uuid": "T2.1" + }, + "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + "mc-config": { + "spectrum": { + "center-frequency": 195000000000000, + "frequency-constraint": { + "adjustment-granularity": "G_6_25GHZ", + "grid-type": "FLEX" + } + } + } + } + }, + { + "direction": "BIDIRECTIONAL", + "layer-protocol-name": "PHOTONIC_MEDIA", + "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + "local-id": "T2.1", + "service-interface-point": { + "service-interface-point-uuid": "T1.1" + }, + "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + "mc-config": { + "spectrum": { + "center-frequency": 195006250, + "frequency-constraint": { + "adjustment-granularity": "G_6_25GHZ", + "grid-type": "FLEX" + } + } + } + } + }, + { + "direction": "BIDIRECTIONAL", + "layer-protocol-name": "PHOTONIC_MEDIA", + "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + "local-id": "T1.2", + "service-interface-point": { + "service-interface-point-uuid": "T2.1" + }, + "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + "mc-config": { + "spectrum": { + "center-frequency": 195018750, + "frequency-constraint": { + "adjustment-granularity": "G_6_25GHZ", + "grid-type": "FLEX" + } + } + } + } + }, + { + "direction": "BIDIRECTIONAL", + "layer-protocol-name": "PHOTONIC_MEDIA", + "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + "local-id": "T2.1", + "service-interface-point": { + "service-interface-point-uuid": "T1.2" + }, + "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + "mc-config": { + "spectrum": { + "center-frequency": 195018750, + "frequency-constraint": { + "adjustment-granularity": "G_6_25GHZ", + "grid-type": "FLEX" + } + } + } + } + }, + { + "direction": "BIDIRECTIONAL", + "layer-protocol-name": "PHOTONIC_MEDIA", + "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + "local-id": "T1.3", + "service-interface-point": { + "service-interface-point-uuid": "T2.1" + }, + "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + "mc-config": { + "spectrum": { + "center-frequency": 195031250, + "frequency-constraint": { + "adjustment-granularity": "G_6_25GHZ", + "grid-type": "FLEX" + } + } + } + } + }, + { + "direction": "BIDIRECTIONAL", + "layer-protocol-name": "PHOTONIC_MEDIA", + "layer-protocol-qualifier": "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_MC", + "local-id": "T2.1", + "service-interface-point": { + "service-interface-point-uuid": "T1.3" + }, + "tapi-photonic-media:media-channel-connectivity-service-end-point-spec": { + "mc-config": { + "spectrum": { + "center-frequency": 195031250, + "frequency-constraint": { + "adjustment-granularity": "G_6_25GHZ", + "grid-type": "FLEX" + } + } + } + } + } + ] + } + ] + } + } # Generate Rules src_name = source # T2.1 @@ -333,15 +304,11 @@ def compute_optical_path(service: Service) -> PathCompReply: network_slice_uuid_str = f"{src_name}_to_{dest_str}" tunnel_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, network_slice_uuid_str)) - device_name = extract_teraflowsdn_device_name(service) provisionamiento = { "network-slice-uuid": network_slice_uuid_str, "viability": True, "actions": [] } - if device_name: - provisionamiento["device_name"] = device_name - try: attributes = resp_json.get("tapi-connectivity:connectivity-service", {}).get("connection", [{}])[0].get("optical-connection-attributes", {}) groups = attributes.get("subcarrier-attributes", {}).get("digital-subcarrier-group", []) -- GitLab From 6e25d4e962b046bc635f4f8499c295f6689755b7 Mon Sep 17 00:00:00 2001 From: armingol Date: Tue, 26 May 2026 16:05:17 +0000 Subject: [PATCH 04/12] fix import --- src/pathcomp/frontend/service/OpticalPathComp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pathcomp/frontend/service/OpticalPathComp.py b/src/pathcomp/frontend/service/OpticalPathComp.py index eaee91c52..453726f64 100644 --- a/src/pathcomp/frontend/service/OpticalPathComp.py +++ b/src/pathcomp/frontend/service/OpticalPathComp.py @@ -1,5 +1,6 @@ import json import requests +import logging import uuid from common.proto.context_pb2 import Service, ConfigRule, ConfigActionEnum from common.proto.pathcomp_pb2 import PathCompReply -- GitLab From ef80f84fd26486bfc2dea7589241b36a91f7e129 Mon Sep 17 00:00:00 2001 From: Pablo Armingol Date: Tue, 26 May 2026 16:20:50 +0000 Subject: [PATCH 05/12] code clean up --- src/pathcomp/frontend/service/OpticalPathComp.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pathcomp/frontend/service/OpticalPathComp.py b/src/pathcomp/frontend/service/OpticalPathComp.py index 453726f64..7a43f7321 100644 --- a/src/pathcomp/frontend/service/OpticalPathComp.py +++ b/src/pathcomp/frontend/service/OpticalPathComp.py @@ -395,8 +395,7 @@ def compute_optical_path(service: Service) -> PathCompReply: "src-ip-mask": str(nodes[src_name]["ip-mask"]), "src-vlan-id": nodes[src_name]["vlan-id"], } - if device_name: - content["device_name"] = device_name + for i, dest in enumerate(dest_list): if dest in nodes: content[f"dest{i+1}-node-uuid"] = dest -- GitLab From c8fc9fe95bef78a94ff99acb3bc110d18e023472 Mon Sep 17 00:00:00 2001 From: armingol Date: Fri, 29 May 2026 13:26:12 +0000 Subject: [PATCH 06/12] feat: integrate device querying functionality and update optical path provisioning with controller IP --- .../frontend/service/OpticalPathComp.py | 7 ++-- .../frontend/service/TopologyTools.py | 36 ++++++++++++++++++- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/pathcomp/frontend/service/OpticalPathComp.py b/src/pathcomp/frontend/service/OpticalPathComp.py index 453726f64..8f7c03a30 100644 --- a/src/pathcomp/frontend/service/OpticalPathComp.py +++ b/src/pathcomp/frontend/service/OpticalPathComp.py @@ -4,6 +4,7 @@ import logging import uuid from common.proto.context_pb2 import Service, ConfigRule, ConfigActionEnum from common.proto.pathcomp_pb2 import PathCompReply +from .TopologyTools import get_device_with_driver LOGGER = logging.getLogger(__name__) @@ -395,20 +396,20 @@ def compute_optical_path(service: Service) -> PathCompReply: "src-ip-mask": str(nodes[src_name]["ip-mask"]), "src-vlan-id": nodes[src_name]["vlan-id"], } - if device_name: - content["device_name"] = device_name + for i, dest in enumerate(dest_list): if dest in nodes: content[f"dest{i+1}-node-uuid"] = dest content[f"dest{i+1}-ip-address"] = nodes[dest]["ip-address"] content[f"dest{i+1}-ip-mask"] = str(nodes[dest]["ip-mask"]) content[f"dest{i+1}-vlan-id"] = nodes[dest]["vlan-id"] + controller_ip = get_device_with_driver() provisionamiento["actions"].append({ "type": "CONFIG_VPNL3", "layer": "IP", "content": content, - "controller-uuid": "IP Controller" + "controller_uuid": controller_ip }) config_rules.append(provisionamiento) diff --git a/src/pathcomp/frontend/service/TopologyTools.py b/src/pathcomp/frontend/service/TopologyTools.py index 036f85a56..cf3e0dd9d 100644 --- a/src/pathcomp/frontend/service/TopologyTools.py +++ b/src/pathcomp/frontend/service/TopologyTools.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, math +import logging, math, os, socket, requests from typing import Dict, Optional from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME, ServiceNameEnum from common.Settings import ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, find_environment_variables, get_env_var_name @@ -25,6 +25,7 @@ from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient from forecaster.client.ForecasterClient import ForecasterClient + LOGGER = logging.getLogger(__name__) def get_service_schedule(service : Service) -> Optional[Constraint_Schedule]: @@ -96,3 +97,36 @@ def get_pathcomp_topology_details(request : PathCompRequest, allow_forecasting : link.attributes.total_capacity_gbps = total_capacity_gbps return topology_details + +# The HOST IP of the TFS controller is needed to query the devices and get the +# IP address of the controller to be used in the path provisioning. +ENVVAR_TFS_API_HOST = '10.95.89.50' + + + +def get_device_with_driver(driver_name: str = 'DEVICEDRIVER_IETF_L3VPN', timeout: int = 10): + """Query the TFS controller REST API and return the _connect/address value of the first device with the given driver. + + Example: GET http:///tfs-api/devices + + Returns the _connect/address string or None. + """ + url = f'http://{ENVVAR_TFS_API_HOST}:80/tfs-api/devices' + try: + logging.debug("Requesting devices from %s", url) + resp = requests.get(url, timeout=timeout) + resp.raise_for_status() + data = resp.json() + except Exception as e: + logging.warning("Error fetching devices from %s: %s", url, e) + return None + + devices = data.get('devices', []) if isinstance(data, dict) else [] + for dev in devices: + drivers = dev.get('device_drivers') or [] + if isinstance(drivers, list) and driver_name in drivers: + device_name = dev.get('name') + return device_name + + logging.info("No device with driver %s found on %s", driver_name, url) + return None -- GitLab From 53a98ebf27719c1a04c5f0f9ef1290e3a5fe5512 Mon Sep 17 00:00:00 2001 From: Pablo Armingol Date: Fri, 29 May 2026 13:32:33 +0000 Subject: [PATCH 07/12] Edit my_deploy.sh --- my_deploy.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/my_deploy.sh b/my_deploy.sh index b791af9fe..3dd19bd0e 100644 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -63,7 +63,7 @@ export TFS_COMPONENTS="context device pathcomp service nbi webui" #export TFS_COMPONENTS="${TFS_COMPONENTS} forecaster" # Uncomment to activate E2E Orchestrator -export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" +# export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" # Uncomment to activate VNT Manager #export TFS_COMPONENTS="${TFS_COMPONENTS} vnt_manager" @@ -143,7 +143,7 @@ export CRDB_PASSWORD="tfs123" export CRDB_DEPLOY_MODE="single" # Disable flag for dropping database, if it exists. -export CRDB_DROP_DATABASE_IF_EXISTS="YES" +export CRDB_DROP_DATABASE_IF_EXISTS="" # Disable flag for re-deploying CockroachDB from scratch. export CRDB_REDEPLOY="" @@ -211,7 +211,7 @@ export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis" export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups" # Disable flag for dropping tables if they exist. -export QDB_DROP_TABLES_IF_EXIST="YES" +export QDB_DROP_TABLES_IF_EXIST="" # Disable flag for re-deploying QuestDB from scratch. export QDB_REDEPLOY="" -- GitLab From 5cbb76491e367f9ec2a7c855300e87509d41c246 Mon Sep 17 00:00:00 2001 From: Pablo Armingol Date: Fri, 29 May 2026 13:32:59 +0000 Subject: [PATCH 08/12] Edit my_deploy.sh --- my_deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/my_deploy.sh b/my_deploy.sh index 3dd19bd0e..bc7e2dc97 100644 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -63,7 +63,7 @@ export TFS_COMPONENTS="context device pathcomp service nbi webui" #export TFS_COMPONENTS="${TFS_COMPONENTS} forecaster" # Uncomment to activate E2E Orchestrator -# export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" +#export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" # Uncomment to activate VNT Manager #export TFS_COMPONENTS="${TFS_COMPONENTS} vnt_manager" -- GitLab From b35bf4859d7986d6b8af49054585266954baa48a Mon Sep 17 00:00:00 2001 From: armingol Date: Fri, 29 May 2026 13:33:31 +0000 Subject: [PATCH 09/12] feat: refactor IPoWDM resource processing to enable request creation --- .../drivers/ietf_l3vpn/IetfL3VpnDriver.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/device/service/drivers/ietf_l3vpn/IetfL3VpnDriver.py b/src/device/service/drivers/ietf_l3vpn/IetfL3VpnDriver.py index 5a1135894..cc78bbbd3 100644 --- a/src/device/service/drivers/ietf_l3vpn/IetfL3VpnDriver.py +++ b/src/device/service/drivers/ietf_l3vpn/IetfL3VpnDriver.py @@ -191,16 +191,15 @@ class IetfL3VpnDriver(_Driver): if len(resources) == 0: return results with self.__lock: if 'ipowdm' in str(resources): - LOGGER.info('Processing IPoWDM resources: {:s}'.format(str(resources))) - # for resource in resources: - # if 'ipowdm' in str(resource): - # try: - # create_request(resource) - # LOGGER.info('Request created successfully') - # results.append((resource, True)) - # except Exception as e: - # MSG = 'Invalid resource_value type: expected dict, got {:s}' - # results.append((resource, e)) + for resource in resources: + if 'ipowdm' in str(resource): + try: + create_request(resource) + LOGGER.info('Request created successfully') + results.append((resource, True)) + except Exception as e: + MSG = 'Invalid resource_value type: expected dict, got {:s}' + results.append((resource, e)) else: for resource in resources: resource_key, resource_value = resource -- GitLab From 0c921ce0980a4294b41a28305638ce3f6cb84648 Mon Sep 17 00:00:00 2001 From: Pablo Armingol Date: Tue, 9 Jun 2026 14:08:03 +0000 Subject: [PATCH 10/12] Edit my_deploy.sh --- my_deploy.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/my_deploy.sh b/my_deploy.sh index b791af9fe..bc7e2dc97 100644 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -63,7 +63,7 @@ export TFS_COMPONENTS="context device pathcomp service nbi webui" #export TFS_COMPONENTS="${TFS_COMPONENTS} forecaster" # Uncomment to activate E2E Orchestrator -export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" +#export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" # Uncomment to activate VNT Manager #export TFS_COMPONENTS="${TFS_COMPONENTS} vnt_manager" @@ -143,7 +143,7 @@ export CRDB_PASSWORD="tfs123" export CRDB_DEPLOY_MODE="single" # Disable flag for dropping database, if it exists. -export CRDB_DROP_DATABASE_IF_EXISTS="YES" +export CRDB_DROP_DATABASE_IF_EXISTS="" # Disable flag for re-deploying CockroachDB from scratch. export CRDB_REDEPLOY="" @@ -211,7 +211,7 @@ export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis" export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups" # Disable flag for dropping tables if they exist. -export QDB_DROP_TABLES_IF_EXIST="YES" +export QDB_DROP_TABLES_IF_EXIST="" # Disable flag for re-deploying QuestDB from scratch. export QDB_REDEPLOY="" -- GitLab From 769d012e79caced35f1b3a079ebf949e346e3252 Mon Sep 17 00:00:00 2001 From: armingol Date: Tue, 9 Jun 2026 14:12:52 +0000 Subject: [PATCH 11/12] refactor: clean up imports and add Apache 2.0 license header to OpticalPathComp.py --- .../frontend/service/OpticalPathComp.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/pathcomp/frontend/service/OpticalPathComp.py b/src/pathcomp/frontend/service/OpticalPathComp.py index 8f7c03a30..ed774fc3b 100644 --- a/src/pathcomp/frontend/service/OpticalPathComp.py +++ b/src/pathcomp/frontend/service/OpticalPathComp.py @@ -1,11 +1,25 @@ +# Copyright 2022-2026 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import json -import requests import logging import uuid -from common.proto.context_pb2 import Service, ConfigRule, ConfigActionEnum + +from common.proto.context_pb2 import ConfigActionEnum, ConfigRule, Service from common.proto.pathcomp_pb2 import PathCompReply -from .TopologyTools import get_device_with_driver +from .TopologyTools import get_device_with_driver LOGGER = logging.getLogger(__name__) -- GitLab From 2ac70abb22e02ff4847bc6a23060de090f26aa85 Mon Sep 17 00:00:00 2001 From: armingol Date: Tue, 9 Jun 2026 14:13:15 +0000 Subject: [PATCH 12/12] feat: add requests library import to OpticalPathComp service --- src/pathcomp/frontend/service/OpticalPathComp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pathcomp/frontend/service/OpticalPathComp.py b/src/pathcomp/frontend/service/OpticalPathComp.py index ed774fc3b..20d4dc5eb 100644 --- a/src/pathcomp/frontend/service/OpticalPathComp.py +++ b/src/pathcomp/frontend/service/OpticalPathComp.py @@ -16,6 +16,7 @@ import json import logging import uuid +import requests from common.proto.context_pb2 import ConfigActionEnum, ConfigRule, Service from common.proto.pathcomp_pb2 import PathCompReply -- GitLab