Commit 9895ccf7 authored by Pablo Armingol's avatar Pablo Armingol
Browse files

feat: implement IPoWDM PUT service handler, enable E2E orchestrator, and...

feat: implement IPoWDM PUT service handler, enable E2E orchestrator, and update deployment configurations
parent 8586676f
Loading
Loading
Loading
Loading
+144 −6
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ class IPoWDMService(Resource):
        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 IPoWDM service: %s", serviceId)
@@ -89,12 +90,150 @@ class IPoWDMService(Resource):
            'device_id': device_id
        }, 201

    def _delete_service_internal(self, serviceId: str, device_id: str):
        LOGGER.info("Deleting old service %s internal...", serviceId)
        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.warning("Failed to delete TFS IPoWDM service: %s", str(e))

        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 %s from device %s", serviceId, device_id)
            except Exception as e:
                LOGGER.warning("Failed to delete %s from device: %s", serviceId, str(e))

    def put(self, serviceId: str):
        LOGGER.info("Received PUT request for IPoWDM service: %s", serviceId)

        raw_json = request.get_json(silent=True) or {}
        old_service_id = raw_json.get("old_service_id")
        request_data = raw_json
        LOGGER.info("IPoWDM request data: %s", json.dumps(request_data, indent=2))

        # Support nested/wrapped payload structure
        if isinstance(request_data, dict) and 'services' in request_data:
            services = request_data['services']
            if isinstance(services, list) and len(services) > 0:
                first_item = services[0]
                if isinstance(first_item, list) and len(first_item) > 0:
                    first_item = first_item[0]
                if isinstance(first_item, dict):
                    request_data = first_item

        if isinstance(request_data, dict) and 'rule_set' in request_data:
            rule_set = request_data['rule_set']
            src_endpoints = rule_set.get('src', [])
            dst_endpoints = rule_set.get('dst', [])
            bandwidth = rule_set.get('bw', 100)
            device_id = rule_set.get('uuid', 'TFS-PACKET')
            if 'endpoint_id' in request_data:
                device_id = request_data['endpoint_id'].get('device_id', {}).get('device_uuid', {}).get('uuid') or device_id
        else:
            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')

        if old_service_id:
            LOGGER.info("Processing update: old service to delete: %s", old_service_id)
            self._delete_service_internal(old_service_id, device_id)

        if not src_endpoints or not dst_endpoints:
            return {'status': 'error', 'message': 'Missing required fields: src and dst'}, 400

        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.UpdateService(service)
            LOGGER.info("Updated TFS IPoWDM service: %s", service_response)

            # Explicitly transition status back to ACTIVE in Context because UpdateService forces PLANNED
            try:
                db_service = self.context_client.GetService(service.service_id)
                db_service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_ACTIVE
                self.context_client.SetService(db_service)
                LOGGER.info("Explicitly set updated service status to ACTIVE in Context")
            except Exception as ex:
                LOGGER.warning("Could not set service status to ACTIVE in Context: %s", str(ex))

        except Exception as e:
            LOGGER.error("Failed to update TFS IPoWDM service: %s", str(e), exc_info=True)
            return {'status': 'error', 'message': f'Failed to update 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 updated for {serviceId}',
            'serviceId': serviceId,
            'device_id': device_id
        }, 200

    def delete(self, serviceId: str):
        LOGGER.info("Received DELETE request for IPoWDM service: %s", serviceId)

        data = request.get_json() or {}
        data = request.get_json(silent=True) or {}
        device_id = data.get('device_id', 'TFS-PACKET')

        device_ip = "10.95.86.67" # Default fallback
        if device_id:
            try:
                from common.proto.context_pb2 import DeviceId
                device_id_obj = DeviceId()
                device_id_obj.device_uuid.uuid = device_id
                device = self.context_client.GetDevice(device_id_obj)
                for rule in device.device_config.config_rules:
                    if rule.custom.resource_key == '_connect/address':
                        device_ip = rule.custom.resource_value
                        break
            except Exception as e:
                LOGGER.warning("Failed to retrieve device IP for %s: %s", device_id, str(e))

        LOGGER.info("Calculated device IP for %s is: %s", device_id, device_ip)

        try:
            from common.proto.context_pb2 import ServiceId

@@ -131,13 +270,12 @@ class IPoWDMService(Resource):
            "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)
            url = f'http://{device_ip}/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)
            url = f'http://{device_ip}/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: