Commit d0e52ec9 authored by Pablo Armingol's avatar Pablo Armingol
Browse files

Merge branch...

Merge branch 'feat/327-tid-new-service-to-ipowdm-controller-to-manage-transceivers-configuration-on-external-agent' of https://labs.etsi.org/rep/tfs/controller into feat/327-tid-new-service-to-ipowdm-controller-to-manage-transceivers-configuration-on-external-agent
parents 7ad48262 fad93dc0
Loading
Loading
Loading
Loading
+4 −14
Original line number Diff line number Diff line
@@ -15,18 +15,8 @@
syntax = "proto3";
package ip_link;

message RuleEndpoint {
    string uuid = 1;
    string ip_address = 2;
    string ip_mask = 3;
    int32 vlan_id = 4;
    float power = 5;
    float frequency = 6;
}

message IpowdmRuleSet {
    repeated RuleEndpoint src = 1;
    repeated RuleEndpoint dst = 2;
    int32 bw = 3;
    string uuid = 4;
message IpLinkRuleSet {
  string  ip   = 1;
  string  mask = 3;
  string  vlan = 4;
}
+1 −1
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ from .handlers.SubscriptionHandler import (
from .Constants import SPECIAL_RESOURCE_MAPPINGS
from .TfsApiClient import TfsApiClient
from .Tools import compose_resource_endpoint

from .templates.tools import create_request

LOGGER = logging.getLogger(__name__)

+0 −88
Original line number Diff line number Diff line
# Copyright 2022-2025 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 logging
import os

LOGGER = logging.getLogger(__name__)

def create_tapi_request(resource_value):
    """Create TAPI connectivity service request from resource value.
    
    Args:
        resource_value: Tuple of (resource_key, resource_data_dict)
        
    Returns:
        dict: TAPI connectivity service request payload
    """
    LOGGER.info("Creating TAPI request for resource_value: %s", resource_value)
    try:
        # Load JSON template
        base_dir = os.path.dirname(os.path.abspath(__file__))
        json_path = os.path.join(base_dir, 'templates', 'lsp.json')
        with open(json_path, 'r', encoding='utf-8') as f:
            template = json.load(f)
        
        # Extract service data from resource
        resource_key = resource_value[0]
        resource_data = json.loads(resource_value[1]) if isinstance(resource_value[1], str) else resource_value[1]
        
        LOGGER.info("Processing resource_key: %s", resource_key)
        LOGGER.info("Resource data: %s", resource_data)
        
        # Populate service-level fields
        svc = template["tapi-connectivity:connectivity-service"][0]
        svc["connectivity-direction"] = resource_data["direction"]
        svc["layer-protocol-name"] = resource_data["layer_protocol_name"]
        svc["layer-protocol-qualifier"] = resource_data["layer_protocol_qualifier"]
        svc["requested-capacity"]["total-size"]["unit"] = "GHz"
        svc["requested-capacity"]["total-size"]["value"] = resource_data["bw"]
        svc["include-link"] = resource_data.get("link_uuid_path", [])
        svc["uuid"] = resource_data["uuid"]
        
        # Configure source endpoint
        ep0 = svc["end-point"][0]
        ep0["service-interface-point"]["service-interface-point-uuid"] = resource_data["input_sip"]
        ep0["direction"] = resource_data["direction"]
        ep0["layer-protocol-name"] = resource_data["layer_protocol_name"]
        ep0["layer-protocol-qualifier"] = resource_data["layer_protocol_qualifier"]
        ep0["local-id"] = resource_data["input_sip"]
        
        # Configure spectrum for source endpoint
        media_spec = ep0["tapi-photonic-media:media-channel-connectivity-service-end-point-spec"]
        mc_config = media_spec["mc-config"]
        spectrum = mc_config["spectrum"]
        spectrum["lower-frequency"] = resource_data["lower_frequency_mhz"]
        spectrum["upper-frequency"] = resource_data["upper_frequency_mhz"]
        spectrum["frequency-constraint"]["adjustment-granularity"] = resource_data["granularity"]
        spectrum["frequency-constraint"]["grid-type"] = resource_data["grid_type"]
        
        # Configure destination endpoint
        ep1 = svc["end-point"][1]
        ep1["service-interface-point"]["service-interface-point-uuid"] = resource_data["output_sip"]
        ep1["direction"] = resource_data["direction"]
        ep1["layer-protocol-name"] = resource_data["layer_protocol_name"]
        ep1["layer-protocol-qualifier"] = resource_data["layer_protocol_qualifier"]
        ep1["local-id"] = resource_data["output_sip"]
        
        LOGGER.info("Created TAPI request: %s", json.dumps(template, indent=2))
        
        # Extract URL if present
        url = resource_data.get("url", "")
        return template, url
        
    except (OSError, json.JSONDecodeError, KeyError) as e:
        LOGGER.error("Error creating TAPI request: %s", str(e), exc_info=True)
        raise
+0 −135
Original line number Diff line number Diff line
# Copyright 2022-2025 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 logging, requests
from typing import Dict, List, Optional
from common.tools.client.RestClient import RestClient
from device.service.driver_api.ImportTopologyEnum import ImportTopologyEnum

GET_CONTEXT_IDS_URL = '/tfs-api/context_ids'
GET_DEVICES_URL     = '/tfs-api/devices'
GET_LINKS_URL       = '/tfs-api/links'

MAPPING_STATUS = {
    'DEVICEOPERATIONALSTATUS_UNDEFINED': 0,
    'DEVICEOPERATIONALSTATUS_DISABLED' : 1,
    'DEVICEOPERATIONALSTATUS_ENABLED'  : 2,
}

MAPPING_DRIVER = {
    'DEVICEDRIVER_UNDEFINED'            : 0,
    'DEVICEDRIVER_OPENCONFIG'           : 1,
    'DEVICEDRIVER_TRANSPORT_API'        : 2,
    'DEVICEDRIVER_P4'                   : 3,
    'DEVICEDRIVER_IETF_NETWORK_TOPOLOGY': 4,
    'DEVICEDRIVER_ONF_TR_532'           : 5,
    'DEVICEDRIVER_XR'                   : 6,
    'DEVICEDRIVER_IETF_L2VPN'           : 7,
    'DEVICEDRIVER_GNMI_OPENCONFIG'      : 8,
    'DEVICEDRIVER_OPTICAL_TFS'          : 9,
    'DEVICEDRIVER_IETF_ACTN'            : 10,
    'DEVICEDRIVER_OC'                   : 11,
    'DEVICEDRIVER_QKD'                  : 12,
    'DEVICEDRIVER_IETF_L3VPN'           : 13,
    'DEVICEDRIVER_IETF_SLICE'           : 14,
    'DEVICEDRIVER_NCE'                  : 15,
    'DEVICEDRIVER_SMARTNIC'             : 16,
    'DEVICEDRIVER_MORPHEUS'             : 17,
    'DEVICEDRIVER_RYU'                  : 18,
}

LOGGER = logging.getLogger(__name__)

class TfsApiClient(RestClient):
    def __init__(
        self, address : str, port : int, scheme : str = 'http',
        username : Optional[str] = None, password : Optional[str] = None,
        timeout : Optional[int] = 30
    ) -> None:
        super().__init__(
            address, port, scheme=scheme, username=username, password=password,
            timeout=timeout, verify_certs=False, allow_redirects=True, logger=LOGGER
        )

    def check_credentials(self) -> None:
        self.get(GET_CONTEXT_IDS_URL, expected_status_codes={requests.codes['OK']})
        LOGGER.info('Credentials checked')

    def get_devices_endpoints(
        self, import_topology : ImportTopologyEnum = ImportTopologyEnum.DEVICES
    ) -> List[Dict]:
        LOGGER.debug('[get_devices_endpoints] begin')
        MSG = '[get_devices_endpoints] import_topology={:s}'
        LOGGER.debug(MSG.format(str(import_topology)))

        if import_topology == ImportTopologyEnum.DISABLED:
            MSG = 'Unsupported import_topology mode: {:s}'
            raise Exception(MSG.format(str(import_topology)))

        devices = self.get(GET_DEVICES_URL, expected_status_codes={requests.codes['OK']})

        result = list()
        for json_device in devices['devices']:
            device_uuid : str = json_device['device_id']['device_uuid']['uuid']
            device_type : str = json_device['device_type']
            device_status = json_device['device_operational_status']
            device_url = '/devices/device[{:s}]'.format(device_uuid)
            device_data = {
                'uuid': json_device['device_id']['device_uuid']['uuid'],
                'name': json_device['name'],
                'type': device_type,
                'status': MAPPING_STATUS[device_status],
                'drivers': [
                    MAPPING_DRIVER[driver]
                    for driver in json_device['device_drivers']
                ],
            }
            result.append((device_url, device_data))

            for json_endpoint in json_device['device_endpoints']:
                endpoint_uuid = json_endpoint['endpoint_id']['endpoint_uuid']['uuid']
                endpoint_url = '/endpoints/endpoint[{:s}]'.format(endpoint_uuid)
                endpoint_data = {
                    'device_uuid': device_uuid,
                    'uuid': endpoint_uuid,
                    'name': json_endpoint['name'],
                    'type': json_endpoint['endpoint_type'],
                }
                result.append((endpoint_url, endpoint_data))

        if import_topology == ImportTopologyEnum.DEVICES:
            LOGGER.debug('[get_devices_endpoints] devices only; returning')
            return result

        links = self.get(GET_LINKS_URL, expected_status_codes={requests.codes['OK']})

        for json_link in links['links']:
            link_uuid : str = json_link['link_id']['link_uuid']['uuid']
            link_url = '/links/link[{:s}]'.format(link_uuid)
            link_endpoint_ids = [
                (
                    json_endpoint_id['device_id']['device_uuid']['uuid'],
                    json_endpoint_id['endpoint_uuid']['uuid'],
                )
                for json_endpoint_id in json_link['link_endpoint_ids']
            ]
            link_data = {
                'uuid': json_link['link_id']['link_uuid']['uuid'],
                'name': json_link['name'],
                'endpoints': link_endpoint_ids,
            }
            result.append((link_url, link_data))

        LOGGER.debug('[get_devices_endpoints] topology; returning')
        return result
+13 −15
Original line number Diff line number Diff line
@@ -170,21 +170,19 @@ def create_connectivity_service(
        ]
    }
    results = []
    LOGGER.info('Would create Connectivity service {:s}: {:s}'.format(str(uuid), str(data)))
    # try:
    #     LOGGER.info('Connectivity service {:s}: {:s}'.format(str(uuid), str(data)))
    #     response = requests.post(
    #         url=url, data=json.dumps(data), timeout=timeout, headers=headers, verify=False, auth=auth)
    #     LOGGER.info('TAPI response: {:s}'.format(str(response)))
    # except Exception as e:  # pylint: disable=broad-except
    #     LOGGER.exception('Exception creating ConnectivityService(uuid={:s}, data={:s})'.format(str(uuid), str(data)))
    #     results.append(e)
    # else:
    #     if response.status_code not in HTTP_OK_CODES:
    #         msg = 'Could not create ConnectivityService(uuid={:s}, data={:s}). status_code={:s} reply={:s}'\
    #         LOGGER.error(msg.format(str(uuid), str(data), str(response.status_code), str(response)))
    #     results.append(response.status_code in HTTP_OK_CODES)
    results.append(True)  # Always return success for now
    try:
        LOGGER.info('Connectivity service {:s}: {:s}'.format(str(uuid), str(data)))
        response = requests.post(
            url=url, data=json.dumps(data), timeout=timeout, headers=headers, verify=False, auth=auth)
        LOGGER.info('TAPI response: {:s}'.format(str(response)))
    except Exception as e:  # pylint: disable=broad-except
        LOGGER.exception('Exception creating ConnectivityService(uuid={:s}, data={:s})'.format(str(uuid), str(data)))
        results.append(e)
    else:
        if response.status_code not in HTTP_OK_CODES:
            msg = 'Could not create ConnectivityService(uuid={:s}, data={:s}). status_code={:s} reply={:s}'
            LOGGER.error(msg.format(str(uuid), str(data), str(response.status_code), str(response)))
        results.append(response.status_code in HTTP_OK_CODES)
    return results

def delete_connectivity_service(root_url, uuid, auth : Optional[HTTPBasicAuth] = None, timeout : Optional[int] = None):
Loading