Commit 2ea59363 authored by Pablo Armingol's avatar Pablo Armingol
Browse files

feat: add OpticalPathComp support for P2MP connectivity services and integrate...

feat: add OpticalPathComp support for P2MP connectivity services and integrate into E2E orchestrator
parent 805d1f1b
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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
+15 −0
Original line number Diff line number Diff line
@@ -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
+422 −0
Original line number Diff line number Diff line
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
+7 −0
Original line number Diff line number Diff line
@@ -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):