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

feat: introduce L3NM service API and device configuration, and enhance IPoWDM...

feat: introduce L3NM service API and device configuration, and enhance IPoWDM service with service type detection and refined resource keys.
parent fad93dc0
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ class EmulatedDriver(_Driver):
    def __init__(self, address : str, port : int, **settings) -> None:
        super().__init__(DRIVER_NAME, address, port, **settings)
        self.__lock = threading.Lock()
        self.__address = address
        self.__initial = TreeNode('.')
        self.__running = TreeNode('.')
        self.__subscriptions = TreeNode('.')
@@ -140,6 +141,13 @@ class EmulatedDriver(_Driver):
                    except: # pylint: disable=bare-except
                        pass

                    if resource_key.startswith('/ipowdm/service/'):
                         LOGGER.info('[%s] Legacy IPoWDM Service Provisioning: Payload=%s', self.__address, str(resource_value))
                    elif resource_key.startswith('/ipowdm/l3nm/'):
                         LOGGER.info('[%s] L3NM Service Provisioning: Payload=%s', self.__address, str(resource_value))
                    elif resource_key.startswith('/ipowdm/pluggables/'):
                         LOGGER.info('[%s] Pluggables Service Provisioning: Payload=%s', self.__address, str(resource_value))

                    set_subnode_value(resolver, self.__running, resource_path, resource_value)

                    match = RE_GET_ENDPOINT_FROM_INTERFACE.match(resource_key)
+2 −4
Original line number Diff line number Diff line
@@ -99,6 +99,7 @@ register_ietf_l2vpn (nbi_app)
register_ietf_l3vpn      (nbi_app)
register_ietf_network    (nbi_app)
register_ietf_nss        (nbi_app)
register_ipowdm          (nbi_app)
register_osm_api         (nbi_app)
register_qkd_app         (nbi_app)
register_restconf_root   (nbi_app)
@@ -106,10 +107,7 @@ register_telemetry_subscription(nbi_app)
register_tfs_api         (nbi_app)
#register_topology_updates(nbi_app) # does not work; check if eventlet-grpc side effects
register_vntm_recommend  (nbi_app)
register_telemetry_subscription(nbi_app)
register_camara_qod      (nbi_app)
register_etsi_api        (nbi_app)
register_ipowdm          (nbi_app)
register_well_known      (nbi_app)
LOGGER.info('All connectors registered')

nbi_app.dump_configuration()
+198 −57
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@

import logging
import json
import grpc
from flask_restful import Resource, request
from common.proto.context_pb2 import ConfigActionEnum, ConfigRule, Device, Service, ServiceTypeEnum, ServiceStatusEnum
from device.client.DeviceClient import DeviceClient
@@ -33,53 +34,55 @@ class IPoWDMService(Resource):
        request_data = request.get_json()
        LOGGER.info("IPoWDM request data: %s", json.dumps(request_data, indent=2))

        # Validate required fields
        if 'src' not in request_data or 'dst' not in request_data:
            return {'status': 'error', 'message': 'Missing required fields: src and dst'}, 400
        # Identify Service Type and Device ID
        device_id = request_data.get('device_id', request_data.get('device', 'TFS-PACKET'))
        service_type = ServiceTypeEnum.SERVICETYPE_L3NM

        if 'ietf-l3vpn-svc:l3vpn-svc' in request_data:
            service_type = ServiceTypeEnum.SERVICETYPE_L3NM
        elif 'config' in request_data:
            service_type = ServiceTypeEnum.SERVICETYPE_IPOWDM

        # Extract key information
        src_endpoints = request_data.get('src', [])
        dst_endpoints = request_data.get('dst', [])
        bandwidth = request_data.get('bw', 100)
        device_id = request_data.get('device_id', 'TFS-PACKET')
        # Validate required fields (Legacy support or new checks?)
        if service_type == ServiceTypeEnum.SERVICETYPE_L3NM and 'ietf-l3vpn-svc:l3vpn-svc' not in request_data:
             if 'src' not in request_data or 'dst' not in request_data:
                 pass

        LOGGER.info(f"Service UUID: {serviceId}")
        LOGGER.info(f"Bandwidth: {bandwidth}")
        LOGGER.info(f"Source endpoints: {len(src_endpoints)}")
        LOGGER.info(f"Destination endpoints: {len(dst_endpoints)}")
        LOGGER.info(f"Device ID: {device_id}")
        LOGGER.info(f"Service Type: {service_type}")

        # Create TFS Service for visibility in WebUI
        # Create TFS Service
        try:
            service = Service()
            service.service_id.service_uuid.uuid = serviceId
            service.service_id.context_id.context_uuid.uuid = "admin"
            service.service_type = ServiceTypeEnum.SERVICETYPE_L3NM
            service.service_type = service_type
            service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_ACTIVE
            service.name = f"IPoWDM-{serviceId}"

            # Create service in TFS
            service_response = self.service_client.CreateService(service)
            LOGGER.info("Created TFS IPoWDM service: %s", service_response)
            
        except grpc.RpcError as e:
            if e.code() != grpc.StatusCode.ALREADY_EXISTS:
                LOGGER.error("Failed to create TFS IPoWDM service: %s", str(e), exc_info=True)
                return {'status': 'error', 'message': f'Failed to create TFS service: {str(e)}'}, 500
            LOGGER.warning("TFS IPoWDM service already exists: %s", serviceId)
        except Exception as e:
            LOGGER.error("Failed to create TFS IPoWDM service: %s", str(e), exc_info=True)
            return {'status': 'error', 'message': f'Failed to create TFS service: {str(e)}'}, 500

        # Send to device for processing
        # Configure Device
        if device_id:
            try:
                device = Device()
                device.device_id.device_uuid.uuid = device_id

            # Create config rule with IPoWDM data
                config_rule = ConfigRule()
                config_rule.action = ConfigActionEnum.CONFIGACTION_SET
            config_rule.custom.resource_key = f'/ipowdm/service/{serviceId}'
            
            # Store the complete IPoWDM configuration
                config_rule.custom.resource_key = f'/ipowdm/service/{serviceId}/{device_id}'
                config_rule.custom.resource_value = json.dumps(request_data)

            # Add rule and configure device
                device.device_config.config_rules.append(config_rule)
                self.device_client.ConfigureDevice(device)
                LOGGER.info("Configured device %s with IPoWDM service %s", device_id, serviceId)
@@ -87,6 +90,8 @@ class IPoWDMService(Resource):
            except Exception as e:
                LOGGER.error("Failed to configure device: %s", str(e))
                return {'status': 'error', 'message': f'Failed to configure device: {str(e)}'}, 500
        else:
             LOGGER.warning("No device_id provided, IPoWDM service not configured on device.")

        return {
            'status': 'success',
@@ -109,7 +114,6 @@ class IPoWDMService(Resource):
            service_id.service_uuid.uuid = serviceId
            service_id.context_id.context_uuid.uuid = "admin"

            # Delete service from TFS
            self.service_client.DeleteService(service_id)
            LOGGER.info("Deleted TFS IPoWDM service: %s", serviceId)

@@ -123,10 +127,9 @@ class IPoWDMService(Resource):
                device = Device()
                device.device_id.device_uuid.uuid = device_id

                # Create DELETE config rule
                config_rule = ConfigRule()
                config_rule.action = ConfigActionEnum.CONFIGACTION_DELETE
                config_rule.custom.resource_key = f'/ipowdm/service/{serviceId}'
                config_rule.custom.resource_key = f'/ipowdm/service/{serviceId}/{device_id}'
                config_rule.custom.resource_value = serviceId

                device.device_config.config_rules.append(config_rule)
@@ -141,3 +144,141 @@ class IPoWDMService(Resource):
            'message': f'IPoWDM service deleted for {serviceId}',
            'serviceId': serviceId
        }, 200

class L3NMService(Resource):
    def __init__(self):
        super().__init__()
        self.device_client = DeviceClient()
        self.service_client = ServiceClient()

    def post(self, serviceId: str):
        LOGGER.info("Received POST request for L3NM service: %s", serviceId)

        request_data = request.get_json()

        device_id = request_data.get('device_id')
        device_ids = set()
        if device_id:
            device_ids.add(device_id)

        if not device_ids:
            try:
                l3vpn_svc = request_data.get('ietf-l3vpn-svc:l3vpn-svc', {})
                sites = l3vpn_svc.get('sites', {}).get('site', [])
                for site in sites:
                    site_id = site.get('site-id')
                    if site_id:
                        device_ids.add(site_id)
                LOGGER.info("Extracted device IDs from payload: %s", device_ids)
            except Exception as e:
                LOGGER.warning("Failed to extract device IDs from payload: %s", str(e))

        LOGGER.info(f"Service UUID: {serviceId}")
        LOGGER.info(f"Target Devices: {device_ids}")

        try:
            service = Service()
            service.service_id.service_uuid.uuid = serviceId
            service.service_id.context_id.context_uuid.uuid = "admin"
            service.service_type = ServiceTypeEnum.SERVICETYPE_L3NM
            service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_ACTIVE
            service.name = f"L3NM-{serviceId}"

            service_response = self.service_client.CreateService(service)
            LOGGER.info("Created TFS L3NM service: %s", service_response)
        except grpc.RpcError as e:
            if e.code() != grpc.StatusCode.ALREADY_EXISTS:
                LOGGER.error("Failed to create TFS L3NM service: %s", str(e), exc_info=True)
                return {'status': 'error', 'message': f'Failed to create TFS service: {str(e)}'}, 500
            LOGGER.warning("TFS L3NM service already exists: %s", serviceId)
        except Exception as e:
            LOGGER.error("Failed to create TFS L3NM service: %s", str(e), exc_info=True)
            return {'status': 'error', 'message': f'Failed to create TFS service: {str(e)}'}, 500

        if device_ids:
            for dev_id in device_ids:
                try:
                    device = Device()
                    device.device_id.device_uuid.uuid = dev_id

                    config_rule = ConfigRule()
                    config_rule.action = ConfigActionEnum.CONFIGACTION_SET
                    config_rule.custom.resource_key = f'/ipowdm/l3nm/{serviceId}/{dev_id}'
                    config_rule.custom.resource_value = json.dumps(request_data)

                    device.device_config.config_rules.append(config_rule)
                    self.device_client.ConfigureDevice(device)
                    LOGGER.info("Configured device %s with L3NM service %s", dev_id, serviceId)
                except Exception as e:
                    LOGGER.error("Failed to configure device %s: %s", dev_id, str(e))

        else:
             LOGGER.warning("No devices identified for L3NM service configuration.")

        return {
            'status': 'success',
            'message': f'L3NM service created for {serviceId}',
            'serviceId': serviceId,
            'device_ids': list(device_ids)
        }, 201

class PluggablesService(Resource):
    def __init__(self):
        super().__init__()
        self.device_client = DeviceClient()
        self.service_client = ServiceClient()

    def post(self, serviceId: str):
        LOGGER.info("Received POST request for Pluggables service: %s", serviceId)

        request_data = request.get_json()

        device_id = request_data.get('device')

        LOGGER.info(f"Service UUID: {serviceId}")
        LOGGER.info(f"Device ID: {device_id}")

        try:
            service = Service()
            service.service_id.service_uuid.uuid = serviceId
            service.service_id.context_id.context_uuid.uuid = "admin"
            service.service_type = ServiceTypeEnum.SERVICETYPE_IPOWDM
            service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_ACTIVE
            service.name = f"Pluggables-{serviceId}"

            service_response = self.service_client.CreateService(service)
            LOGGER.info("Created TFS Pluggables service: %s", service_response)
        except grpc.RpcError as e:
            if e.code() != grpc.StatusCode.ALREADY_EXISTS:
                LOGGER.error("Failed to create TFS Pluggables service: %s", str(e), exc_info=True)
                return {'status': 'error', 'message': f'Failed to create TFS service: {str(e)}'}, 500
            LOGGER.warning("TFS Pluggables service already exists: %s", serviceId)
        except Exception as e:
            LOGGER.error("Failed to create TFS Pluggables service: %s", str(e), exc_info=True)
            return {'status': 'error', 'message': f'Failed to create TFS service: {str(e)}'}, 500

        if device_id:
            try:
                device = Device()
                device.device_id.device_uuid.uuid = device_id

                config_rule = ConfigRule()
                config_rule.action = ConfigActionEnum.CONFIGACTION_SET
                config_rule.custom.resource_key = f'/ipowdm/pluggables/{serviceId}/{device_id}'
                config_rule.custom.resource_value = json.dumps(request_data)

                device.device_config.config_rules.append(config_rule)
                self.device_client.ConfigureDevice(device)
                LOGGER.info("Configured device %s with Pluggables service %s", device_id, serviceId)
            except Exception as e:
                LOGGER.error("Failed to configure device: %s", str(e))
                return {'status': 'error', 'message': f'Failed to configure device: {str(e)}'}, 500
        else:
            LOGGER.warning("No device_id provided for Pluggables service.")

        return {
            'status': 'success',
            'message': f'Pluggables service created for {serviceId}',
            'serviceId': serviceId,
            'device_id': device_id
        }, 201
+11 −1
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@
# limitations under the License.

import logging
from .Resources import IPoWDMService
from .Resources import IPoWDMService, L3NMService, PluggablesService

LOGGER = logging.getLogger(__name__)

@@ -26,4 +26,14 @@ def register_ipowdm(nbi_app):
        f'{URL_PREFIX}/service/<string:serviceId>',
        endpoint='ipowdm_service'
    )
    nbi_app.add_rest_api_resource(
        L3NMService,
        f'{URL_PREFIX}/l3nm/<string:serviceId>',
        endpoint='ipowdm_l3nm_service'
    )
    nbi_app.add_rest_api_resource(
        PluggablesService,
        f'{URL_PREFIX}/pluggables/<string:serviceId>',
        endpoint='ipowdm_pluggables_service'
    )
    LOGGER.info('IPoWDM Service NBI registered')