Loading src/device/service/drivers/ietf_l3vpn/templates/ipowdm.json +48 −11 Viewed Changes for src/device/service/drivers/ietf_l3vpn/templates/ipowdm.json: 48 added lines, 11 removed lines. Original line number Diff line number Diff line Loading @@ -2,28 +2,65 @@ "services": [ { "service_id": { "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "IPoWDM"} "context_id": { "context_uuid": { "uuid": "admin" } }, "service_uuid": { "uuid": "IPoWDM" } }, "service_type": 12, "service_status": {"service_status": 1}, "service_status": { "service_status": 1 }, "service_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "IP1"}},"endpoint_uuid": {"uuid": "PORT-xe4"}}, {"device_id": {"device_uuid": {"uuid": "IP2"}},"endpoint_uuid": {"uuid": "PORT-xe4"}} { "device_id": { "device_uuid": { "uuid": "IP1" } }, "endpoint_uuid": { "uuid": "PORT-xe4" } }, { "device_id": { "device_uuid": { "uuid": "IP2" } }, "endpoint_uuid": { "uuid": "PORT-xe4" } } ], "service_constraints": [], "service_config": {"config_rules": [ {"action": 1, "ipowdm": { "service_config": { "config_rules": [ { "action": 1, "ipowdm": { "endpoint_id": { "device_id": {"device_uuid": {"uuid": "IP1"}}, "endpoint_uuid": {"uuid": "PORT-xe4"} "device_id": { "device_uuid": { "uuid": "IP1" } }, "endpoint_uuid": { "uuid": "PORT-xe4" } }, "rule_set": { "src": [], "dst": [] } }} ]} } } ] } } ] } No newline at end of file src/nbi/service/ipowdm/Resources.py 0 → 100644 +406 −0 Viewed Changes for src/nbi/service/ipowdm/Resources.py: 406 added lines, 0 removed lines. 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 requests from flask_restful import Resource, request from common.proto.context_pb2 import ConfigActionEnum, ConfigRule, Device, Service, ServiceTypeEnum, ServiceStatusEnum, ContextId from device.client.DeviceClient import DeviceClient from service.client.ServiceClient import ServiceClient from context.client.ContextClient import ContextClient LOGGER = logging.getLogger(__name__) class IPoWDMService(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 IPoWDM service: %s", serviceId) request_data = request.get_json() LOGGER.info("IPoWDM request data: %s", json.dumps(request_data, indent=2)) if 'src' not in request_data or 'dst' not in request_data: return {'status': 'error', 'message': 'Missing required fields: src and dst'}, 400 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') 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}") 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"IPoWDM-{serviceId}" service_response = self.service_client.CreateService(service) LOGGER.info("Created TFS IPoWDM service: %s", service_response) 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 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/service/{serviceId}' 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 IPoWDM 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 return { 'status': 'success', 'message': f'IPoWDM service created for {serviceId}', 'serviceId': serviceId, 'device_id': device_id }, 201 def delete(self, serviceId: str): LOGGER.info("Received DELETE request for IPoWDM service: %s", serviceId) data = request.get_json() or {} device_id = data.get('device_id', 'TFS-PACKET') try: from common.proto.context_pb2 import ServiceId service_id = ServiceId() service_id.service_uuid.uuid = serviceId service_id.context_id.context_uuid.uuid = "admin" self.service_client.DeleteService(service_id) LOGGER.info("Deleted TFS IPoWDM service: %s", serviceId) except Exception as e: LOGGER.error("Failed to delete TFS IPoWDM service: %s", str(e), exc_info=True) return {'status': 'error', 'message': f'Failed to delete 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_DELETE config_rule.custom.resource_key = f'/ipowdm/service/{serviceId}' config_rule.custom.resource_value = serviceId device.device_config.config_rules.append(config_rule) self.device_client.ConfigureDevice(device) LOGGER.info("Deleted IPoWDM service from device %s", device_id) except Exception as e: LOGGER.warning("Failed to delete from device: %s", str(e)) headers = { "Content-Type": "application/json", "Expect": "" } try: # TODO Dynamic IP address url = f'http://10.95.86.62/restconf/ipowdm/v1/pluggables/{serviceId}' requests.delete(url, headers=headers, timeout=10) LOGGER.info("Deleted pluggables from controller %s: %s", serviceId, url) url = f'http://10.95.86.62/restconf/ipowdm/v1/l3nm/{serviceId}' requests.delete(url, headers=headers, timeout=10) LOGGER.info("Deleted services from controller %s: %s", serviceId, url) except Exception as e: LOGGER.warning("Failed to delete from controller: %s", str(e)) return {'status': 'error', 'message': f'Failed to delete from controller: {str(e)}'}, 500 return { 'status': 'success', 'message': f'IPoWDM service deleted for {serviceId}', 'serviceId': serviceId }, 200 class PluggablesService(Resource): def __init__(self): super().__init__() self.device_client = DeviceClient() self.service_client = ServiceClient() self.context_client = ContextClient() 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 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 def delete(self, serviceId: str): LOGGER.info("Received DELETE request for Pluggables service: %s", serviceId) try: context_id = ContextId() context_id.context_uuid.uuid = "admin" services = self.context_client.ListServices(context_id) services_to_delete = [] endpoints_data = [] for service in services.services: if serviceId in service.name and "Pluggables-" in service.name: LOGGER.info("Found matching Pluggables service to delete: %s", service.name) services_to_delete.append(service.service_id) try: if "-pluggable-" in service.name: parts = service.name.split("-pluggable-") device_name = parts[-1] service_suffix = service.name.replace("Pluggables-", "", 1) resource_key_to_find = f'/ipowdm/pluggables/{service_suffix}/{device_name}' LOGGER.info("Searching for config rule with key: %s on device: %s", resource_key_to_find, device_name) from common.proto.context_pb2 import DeviceId device_id_obj = DeviceId() device_id_obj.device_uuid.uuid = device_name try: device = self.context_client.GetDevice(device_id_obj) rule_found = False for rule in device.device_config.config_rules: if rule.custom.resource_key == resource_key_to_find: LOGGER.info("Found Config Rule Payload: %s", rule.custom.resource_value) rule_found = True try: config_data = json.loads(rule.custom.resource_value) router_id = config_data.get('device') router_tp = config_data.get('config', {}).get('name') if router_id and router_tp: endpoints_data.append({ 'router_id': router_id, 'router_tp': router_tp }) except Exception as e: LOGGER.warning("Failed to parse config rule JSON: %s", str(e)) break if not rule_found: LOGGER.warning("Config rule not found for key: %s", resource_key_to_find) except Exception as e: LOGGER.warning("Failed to get device %s to read config rule: %s", device_name, str(e)) except Exception as e: LOGGER.warning("Error while trying to log config rule for service %s: %s", service.name, str(e)) if len(endpoints_data) == 2: endpoints_data.sort(key=lambda x: x['router_id']) combined_data = { 'src_router_id': endpoints_data[0]['router_id'], 'src_router_tp': endpoints_data[0]['router_tp'], 'dst_router_id': endpoints_data[1]['router_id'], 'dst_router_tp': endpoints_data[1]['router_tp'] } LOGGER.info("Aggregated Config Rules: %s", json.dumps(combined_data, indent=2)) # TODO Dynamic IP address url = "http://192.168.88.17:9849/api-v0/transponders" headers = {'Content-Type': 'application/json'} response = requests.post(url, json=combined_data, headers=headers) LOGGER.info('Pluggables Service Provisioning Response: %s', str(response.text)) elif len(endpoints_data) > 0: LOGGER.warning("Found %d endpoints, expected 2 for aggregation. Data: %s", len(endpoints_data), endpoints_data) deleted_count = 0 for service_id_to_del in services_to_delete: self.service_client.DeleteService(service_id_to_del) deleted_count += 1 LOGGER.info("Deleted %d matching Pluggables services for UUID: %s", deleted_count, serviceId) if deleted_count == 0: LOGGER.warning("No matching Pluggables services found for UUID: %s", serviceId) except Exception as e: LOGGER.error("Failed to delete TFS Pluggables service: %s", str(e), exc_info=True) return {'status': 'error', 'message': f'Failed to delete TFS service: {str(e)}'}, 500 return { 'status': 'success', 'message': f'Pluggables service deleted for {serviceId}', 'serviceId': serviceId }, 200 class L3NMService(Resource): def __init__(self): super().__init__() self.device_client = DeviceClient() self.service_client = ServiceClient() self.context_client = ContextClient() def post(self, serviceId: str): LOGGER.info("Received POST request for L3NM service: %s", serviceId) request_data = request.get_json() device_ids = set() try: l3vpn_svc = request_data.get('ietf-l3vpn-svc:l3vpn-svc', {}) sites = l3vpn_svc.get('sites', {}).get('site', []) for site in sites: site_devices = site.get('devices', {}).get('device', []) for device in site_devices: dev_id = device.get('device-id') 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 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: target_device_id = list(device_ids)[0] try: device = Device() device.device_id.device_uuid.uuid = target_device_id config_rule = ConfigRule() config_rule.action = ConfigActionEnum.CONFIGACTION_SET config_rule.custom.resource_key = f'/ipowdm/l3nm/{serviceId}/{target_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 L3NM service %s", target_device_id, serviceId) except Exception as e: LOGGER.error("Failed to configure device %s: %s", target_device_id, str(e)) return {'status': 'error', 'message': f'Failed to configure device: {str(e)}'}, 500 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 def delete(self, serviceId: str): LOGGER.info("Received DELETE request for L3NM service: %s", serviceId) try: context_id = ContextId() context_id.context_uuid.uuid = "admin" services = self.context_client.ListServices(context_id) deleted_count = 0 for service in services.services: if serviceId in service.name and "L3NM-" in service.name: LOGGER.info("Found matching L3NM service to delete: %s", service.name) self.service_client.DeleteService(service.service_id) deleted_count += 1 LOGGER.info("Deleted %d matching L3NM services for UUID: %s", deleted_count, serviceId) if deleted_count == 0: LOGGER.warning("No matching L3NM services found for UUID: %s", serviceId) except Exception as e: LOGGER.error("Failed to delete TFS L3NM service: %s", str(e), exc_info=True) return {'status': 'error', 'message': f'Failed to delete TFS service: {str(e)}'}, 500 return { 'status': 'success', 'message': f'L3NM service deleted for {serviceId}', 'serviceId': serviceId }, 200 src/nbi/service/ipowdm/__init__.py 0 → 100644 +39 −0 Viewed Changes for src/nbi/service/ipowdm/__init__.py: 39 added lines, 0 removed lines. 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 from .Resources import IPoWDMService, PluggablesService, L3NMService LOGGER = logging.getLogger(__name__) URL_PREFIX = '/restconf/ipowdm/v1' def register_ipowdm(nbi_app): LOGGER.info('Registering IPoWDM Service NBI') nbi_app.add_rest_api_resource( IPoWDMService, f'{URL_PREFIX}/service/<string:serviceId>', endpoint='ipowdm_service' ) nbi_app.add_rest_api_resource( PluggablesService, f'{URL_PREFIX}/pluggables/<string:serviceId>', endpoint='ipowdm_pluggables' ) nbi_app.add_rest_api_resource( L3NMService, f'{URL_PREFIX}/l3nm/<string:serviceId>', endpoint='ipowdm_l3nm' ) LOGGER.info('IPoWDM Service NBI registered') src/nbi/service/app.py +2 −0 Viewed Changes for src/nbi/service/app.py: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ from .ietf_l2vpn import register_ietf_l2vpn from .ietf_l3vpn import register_ietf_l3vpn from .ietf_network import register_ietf_network from .ietf_network_slice import register_ietf_nss from .ipowdm import register_ipowdm from .optical_slice import register_optical_slice from .osm_nbi import register_osm_api from .qkd_app import register_qkd_app Loading Loading @@ -107,6 +108,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_ipowdm (nbi_app) register_media_channel (nbi_app) register_well_known (nbi_app) LOGGER.info('All connectors registered') Loading Loading
src/device/service/drivers/ietf_l3vpn/templates/ipowdm.json +48 −11 Viewed Changes for src/device/service/drivers/ietf_l3vpn/templates/ipowdm.json: 48 added lines, 11 removed lines. Original line number Diff line number Diff line Loading @@ -2,28 +2,65 @@ "services": [ { "service_id": { "context_id": {"context_uuid": {"uuid": "admin"}}, "service_uuid": {"uuid": "IPoWDM"} "context_id": { "context_uuid": { "uuid": "admin" } }, "service_uuid": { "uuid": "IPoWDM" } }, "service_type": 12, "service_status": {"service_status": 1}, "service_status": { "service_status": 1 }, "service_endpoint_ids": [ {"device_id": {"device_uuid": {"uuid": "IP1"}},"endpoint_uuid": {"uuid": "PORT-xe4"}}, {"device_id": {"device_uuid": {"uuid": "IP2"}},"endpoint_uuid": {"uuid": "PORT-xe4"}} { "device_id": { "device_uuid": { "uuid": "IP1" } }, "endpoint_uuid": { "uuid": "PORT-xe4" } }, { "device_id": { "device_uuid": { "uuid": "IP2" } }, "endpoint_uuid": { "uuid": "PORT-xe4" } } ], "service_constraints": [], "service_config": {"config_rules": [ {"action": 1, "ipowdm": { "service_config": { "config_rules": [ { "action": 1, "ipowdm": { "endpoint_id": { "device_id": {"device_uuid": {"uuid": "IP1"}}, "endpoint_uuid": {"uuid": "PORT-xe4"} "device_id": { "device_uuid": { "uuid": "IP1" } }, "endpoint_uuid": { "uuid": "PORT-xe4" } }, "rule_set": { "src": [], "dst": [] } }} ]} } } ] } } ] } No newline at end of file
src/nbi/service/ipowdm/Resources.py 0 → 100644 +406 −0 Viewed Changes for src/nbi/service/ipowdm/Resources.py: 406 added lines, 0 removed lines. 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 requests from flask_restful import Resource, request from common.proto.context_pb2 import ConfigActionEnum, ConfigRule, Device, Service, ServiceTypeEnum, ServiceStatusEnum, ContextId from device.client.DeviceClient import DeviceClient from service.client.ServiceClient import ServiceClient from context.client.ContextClient import ContextClient LOGGER = logging.getLogger(__name__) class IPoWDMService(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 IPoWDM service: %s", serviceId) request_data = request.get_json() LOGGER.info("IPoWDM request data: %s", json.dumps(request_data, indent=2)) if 'src' not in request_data or 'dst' not in request_data: return {'status': 'error', 'message': 'Missing required fields: src and dst'}, 400 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') 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}") 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"IPoWDM-{serviceId}" service_response = self.service_client.CreateService(service) LOGGER.info("Created TFS IPoWDM service: %s", service_response) 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 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/service/{serviceId}' 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 IPoWDM 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 return { 'status': 'success', 'message': f'IPoWDM service created for {serviceId}', 'serviceId': serviceId, 'device_id': device_id }, 201 def delete(self, serviceId: str): LOGGER.info("Received DELETE request for IPoWDM service: %s", serviceId) data = request.get_json() or {} device_id = data.get('device_id', 'TFS-PACKET') try: from common.proto.context_pb2 import ServiceId service_id = ServiceId() service_id.service_uuid.uuid = serviceId service_id.context_id.context_uuid.uuid = "admin" self.service_client.DeleteService(service_id) LOGGER.info("Deleted TFS IPoWDM service: %s", serviceId) except Exception as e: LOGGER.error("Failed to delete TFS IPoWDM service: %s", str(e), exc_info=True) return {'status': 'error', 'message': f'Failed to delete 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_DELETE config_rule.custom.resource_key = f'/ipowdm/service/{serviceId}' config_rule.custom.resource_value = serviceId device.device_config.config_rules.append(config_rule) self.device_client.ConfigureDevice(device) LOGGER.info("Deleted IPoWDM service from device %s", device_id) except Exception as e: LOGGER.warning("Failed to delete from device: %s", str(e)) headers = { "Content-Type": "application/json", "Expect": "" } try: # TODO Dynamic IP address url = f'http://10.95.86.62/restconf/ipowdm/v1/pluggables/{serviceId}' requests.delete(url, headers=headers, timeout=10) LOGGER.info("Deleted pluggables from controller %s: %s", serviceId, url) url = f'http://10.95.86.62/restconf/ipowdm/v1/l3nm/{serviceId}' requests.delete(url, headers=headers, timeout=10) LOGGER.info("Deleted services from controller %s: %s", serviceId, url) except Exception as e: LOGGER.warning("Failed to delete from controller: %s", str(e)) return {'status': 'error', 'message': f'Failed to delete from controller: {str(e)}'}, 500 return { 'status': 'success', 'message': f'IPoWDM service deleted for {serviceId}', 'serviceId': serviceId }, 200 class PluggablesService(Resource): def __init__(self): super().__init__() self.device_client = DeviceClient() self.service_client = ServiceClient() self.context_client = ContextClient() 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 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 def delete(self, serviceId: str): LOGGER.info("Received DELETE request for Pluggables service: %s", serviceId) try: context_id = ContextId() context_id.context_uuid.uuid = "admin" services = self.context_client.ListServices(context_id) services_to_delete = [] endpoints_data = [] for service in services.services: if serviceId in service.name and "Pluggables-" in service.name: LOGGER.info("Found matching Pluggables service to delete: %s", service.name) services_to_delete.append(service.service_id) try: if "-pluggable-" in service.name: parts = service.name.split("-pluggable-") device_name = parts[-1] service_suffix = service.name.replace("Pluggables-", "", 1) resource_key_to_find = f'/ipowdm/pluggables/{service_suffix}/{device_name}' LOGGER.info("Searching for config rule with key: %s on device: %s", resource_key_to_find, device_name) from common.proto.context_pb2 import DeviceId device_id_obj = DeviceId() device_id_obj.device_uuid.uuid = device_name try: device = self.context_client.GetDevice(device_id_obj) rule_found = False for rule in device.device_config.config_rules: if rule.custom.resource_key == resource_key_to_find: LOGGER.info("Found Config Rule Payload: %s", rule.custom.resource_value) rule_found = True try: config_data = json.loads(rule.custom.resource_value) router_id = config_data.get('device') router_tp = config_data.get('config', {}).get('name') if router_id and router_tp: endpoints_data.append({ 'router_id': router_id, 'router_tp': router_tp }) except Exception as e: LOGGER.warning("Failed to parse config rule JSON: %s", str(e)) break if not rule_found: LOGGER.warning("Config rule not found for key: %s", resource_key_to_find) except Exception as e: LOGGER.warning("Failed to get device %s to read config rule: %s", device_name, str(e)) except Exception as e: LOGGER.warning("Error while trying to log config rule for service %s: %s", service.name, str(e)) if len(endpoints_data) == 2: endpoints_data.sort(key=lambda x: x['router_id']) combined_data = { 'src_router_id': endpoints_data[0]['router_id'], 'src_router_tp': endpoints_data[0]['router_tp'], 'dst_router_id': endpoints_data[1]['router_id'], 'dst_router_tp': endpoints_data[1]['router_tp'] } LOGGER.info("Aggregated Config Rules: %s", json.dumps(combined_data, indent=2)) # TODO Dynamic IP address url = "http://192.168.88.17:9849/api-v0/transponders" headers = {'Content-Type': 'application/json'} response = requests.post(url, json=combined_data, headers=headers) LOGGER.info('Pluggables Service Provisioning Response: %s', str(response.text)) elif len(endpoints_data) > 0: LOGGER.warning("Found %d endpoints, expected 2 for aggregation. Data: %s", len(endpoints_data), endpoints_data) deleted_count = 0 for service_id_to_del in services_to_delete: self.service_client.DeleteService(service_id_to_del) deleted_count += 1 LOGGER.info("Deleted %d matching Pluggables services for UUID: %s", deleted_count, serviceId) if deleted_count == 0: LOGGER.warning("No matching Pluggables services found for UUID: %s", serviceId) except Exception as e: LOGGER.error("Failed to delete TFS Pluggables service: %s", str(e), exc_info=True) return {'status': 'error', 'message': f'Failed to delete TFS service: {str(e)}'}, 500 return { 'status': 'success', 'message': f'Pluggables service deleted for {serviceId}', 'serviceId': serviceId }, 200 class L3NMService(Resource): def __init__(self): super().__init__() self.device_client = DeviceClient() self.service_client = ServiceClient() self.context_client = ContextClient() def post(self, serviceId: str): LOGGER.info("Received POST request for L3NM service: %s", serviceId) request_data = request.get_json() device_ids = set() try: l3vpn_svc = request_data.get('ietf-l3vpn-svc:l3vpn-svc', {}) sites = l3vpn_svc.get('sites', {}).get('site', []) for site in sites: site_devices = site.get('devices', {}).get('device', []) for device in site_devices: dev_id = device.get('device-id') 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 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: target_device_id = list(device_ids)[0] try: device = Device() device.device_id.device_uuid.uuid = target_device_id config_rule = ConfigRule() config_rule.action = ConfigActionEnum.CONFIGACTION_SET config_rule.custom.resource_key = f'/ipowdm/l3nm/{serviceId}/{target_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 L3NM service %s", target_device_id, serviceId) except Exception as e: LOGGER.error("Failed to configure device %s: %s", target_device_id, str(e)) return {'status': 'error', 'message': f'Failed to configure device: {str(e)}'}, 500 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 def delete(self, serviceId: str): LOGGER.info("Received DELETE request for L3NM service: %s", serviceId) try: context_id = ContextId() context_id.context_uuid.uuid = "admin" services = self.context_client.ListServices(context_id) deleted_count = 0 for service in services.services: if serviceId in service.name and "L3NM-" in service.name: LOGGER.info("Found matching L3NM service to delete: %s", service.name) self.service_client.DeleteService(service.service_id) deleted_count += 1 LOGGER.info("Deleted %d matching L3NM services for UUID: %s", deleted_count, serviceId) if deleted_count == 0: LOGGER.warning("No matching L3NM services found for UUID: %s", serviceId) except Exception as e: LOGGER.error("Failed to delete TFS L3NM service: %s", str(e), exc_info=True) return {'status': 'error', 'message': f'Failed to delete TFS service: {str(e)}'}, 500 return { 'status': 'success', 'message': f'L3NM service deleted for {serviceId}', 'serviceId': serviceId }, 200
src/nbi/service/ipowdm/__init__.py 0 → 100644 +39 −0 Viewed Changes for src/nbi/service/ipowdm/__init__.py: 39 added lines, 0 removed lines. 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 from .Resources import IPoWDMService, PluggablesService, L3NMService LOGGER = logging.getLogger(__name__) URL_PREFIX = '/restconf/ipowdm/v1' def register_ipowdm(nbi_app): LOGGER.info('Registering IPoWDM Service NBI') nbi_app.add_rest_api_resource( IPoWDMService, f'{URL_PREFIX}/service/<string:serviceId>', endpoint='ipowdm_service' ) nbi_app.add_rest_api_resource( PluggablesService, f'{URL_PREFIX}/pluggables/<string:serviceId>', endpoint='ipowdm_pluggables' ) nbi_app.add_rest_api_resource( L3NMService, f'{URL_PREFIX}/l3nm/<string:serviceId>', endpoint='ipowdm_l3nm' ) LOGGER.info('IPoWDM Service NBI registered')
src/nbi/service/app.py +2 −0 Viewed Changes for src/nbi/service/app.py: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ from .ietf_l2vpn import register_ietf_l2vpn from .ietf_l3vpn import register_ietf_l3vpn from .ietf_network import register_ietf_network from .ietf_network_slice import register_ietf_nss from .ipowdm import register_ipowdm from .optical_slice import register_optical_slice from .osm_nbi import register_osm_api from .qkd_app import register_qkd_app Loading Loading @@ -107,6 +108,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_ipowdm (nbi_app) register_media_channel (nbi_app) register_well_known (nbi_app) LOGGER.info('All connectors registered') Loading