Commit e2a32baf authored by Leandro Campos's avatar Leandro Campos
Browse files

First Functionality

parent ee95258b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import "context.proto";
service E2EOrchestratorService {
  rpc Compute(E2EOrchestratorRequest) returns (E2EOrchestratorReply)  {}
  rpc PushTopology(context.Topology)  returns (context.Empty)         {}
  rpc RunPathAlgorithm(E2EOrchestratorRequest) returns (E2EOrchestratorReply) {}
}

message E2EOrchestratorRequest {
+19 −0
Original line number Diff line number Diff line
@@ -67,3 +67,22 @@ class E2EOrchestratorClient:
            "Compute result: {:s}".format(str(grpc_message_to_json(response)))
        )
        return response

    def RunPathAlgorithm(self, service: Service) -> E2EOrchestratorReply:
        #request = E2EOrchestratorRequest(service=service)
        #LOGGER.info("PathAlgorithm request: %s", grpc_message_to_json(request))
        #response = self.stub.RunPathAlgorithm(request)  # si el proto define RunPathAlgorithm
        #LOGGER.info("PathAlgorithm result: %s", grpc_message_to_json(response))
        #return response


       
        LOGGER.info("Llegó a RunPathAlgorithm con service: %s", service.service_id.service_uuid.uuid)
        # Retorno simulado para no bloquear
        class FakeReply:
            def __init__(self):
                self.message = "Prueba completada"
        return FakeReply()

        
        return response
+113 −0
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 copy, deepmerge, json, logging
from typing import Dict
from flask_restful import Resource, request
from werkzeug.exceptions import UnsupportedMediaType
from common.Constants import DEFAULT_CONTEXT_NAME
from context.client.ContextClient import ContextClient
from service.client.ServiceClient import ServiceClient
from .Tools import (
    format_grpc_to_json, grpc_context_id, grpc_service_id, e2epathcomp_2_service, service_2_e2epathcomp
)

LOGGER = logging.getLogger(__name__)


class _Resource(Resource):
    def __init__(self) -> None:
        super().__init__()
        self.client = ContextClient()
        self.service_client = ServiceClient()


class E2epathcomp(_Resource):
    def get(self):
        service_list = self.client.ListServices(grpc_context_id(DEFAULT_CONTEXT_NAME))
        bw_allocations = [service_2_e2epathcomp(service) for service in service_list.services]
        return bw_allocations

    def post(self):
        if not request.is_json:
            raise UnsupportedMediaType('JSON payload is required')
        
        data: Dict = request.get_json()
        origen = data.get('origen')
        destino = data.get('destino')
        bw = data.get('bw')
        app_ins_id = data.get('appInsId', 'tmp_uuid')

        if not all([origen, destino, bw]):
            return {"error": "Los campos 'origen', 'destino' y 'bw' son obligatorios"}, 400

        # Construir el Service usando los datos recibidos
        service_payload = {
            'origen': origen,
            'destino': destino,
            'fixedAllocation': bw,
            'appInsId': app_ins_id
        }


        service = e2epathcomp_2_service(self.client, service_payload)

        try:
            # Llamar a PathAlgorithm en vez de crear o actualizar el servicio directamente
            from common.client.E2eOrchestratorClient import E2EOrchestratorClient
            e2e_client = E2EOrchestratorClient()
            response = e2e_client.RunPathAlgorithm(service)  # Llamada al algoritmo de PathAlgorithm
            response_json = format_grpc_to_json(response)
            e2e_client.close()
        except Exception as e:  # pylint: disable=broad-except
            LOGGER.exception("Error ejecutando PathAlgorithm")
            return {"error": str(e)}, 500

        return response_json


class E2epathcompId(_Resource):

    def get(self, allocationId: str):
        service = self.client.GetService(grpc_service_id(DEFAULT_CONTEXT_NAME, allocationId))
        return service_2_e2epathcomp(service)

    def put(self, allocationId: str):
        json_data = json.loads(request.get_json())
        service = e2epathcomp_2_service(self.client, json_data)
        self.service_client.UpdateService(service)
        service = self.client.GetService(grpc_service_id(DEFAULT_CONTEXT_NAME, json_data['appInsId']))
        response_bwm = service_2_e2epathcomp(service)

        return response_bwm

    def patch(self, allocationId: str):
        json_data = request.get_json()
        if not 'appInsId' in json_data:
            json_data['appInsId'] = allocationId
        
        service = self.client.GetService(grpc_service_id(DEFAULT_CONTEXT_NAME, json_data['appInsId']))
        current_bwm = service_2_e2epathcomp(service)
        new_bmw = deepmerge.always_merger.merge(current_bwm, json_data)
        
        service = e2epathcomp_2_service(self.client, new_bmw)
        self.service_client.UpdateService(service)

        service = self.client.GetService(grpc_service_id(DEFAULT_CONTEXT_NAME, json_data['appInsId']))
        response_bwm = service_2_e2epathcomp(service)

        return response_bwm

    def delete(self, allocationId: str):
        self.service_client.DeleteService(grpc_service_id(DEFAULT_CONTEXT_NAME, allocationId))
+109 −0
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, logging, time
from decimal import ROUND_HALF_EVEN, Decimal
from flask.json import jsonify
from common.proto.context_pb2 import (
    ContextId, ServiceId, ServiceStatusEnum, ServiceTypeEnum,
    Service, Constraint, Constraint_SLA_Capacity, ConfigRule, ConfigRule_Custom,
    ConfigActionEnum
)
from common.tools.grpc.Tools import grpc_message_to_json
from common.tools.object_factory.Context import json_context_id
from common.tools.object_factory.Service import json_service_id

LOGGER = logging.getLogger(__name__)

# ---------- Funciones adaptadas al flujo E2E/PathComputation ----------

def service_2_e2epathcomp(service: Service) -> dict:
    """Extrae la información mínima de un Service para E2E"""
    info = {
        "appInsId": service.service_id.service_uuid.uuid,
        "fixedAllocation": None,
        "origen": None,
        "destino": None
    }

    # Extraer ancho de banda
    for c in service.service_constraints:
        if c.WhichOneof('constraint') == 'sla_capacity':
            fixed_allocation = Decimal(c.sla_capacity.capacity_gbps * 1.e9)
            info["fixedAllocation"] = str(fixed_allocation.quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
            break

    # Extraer origen/destino
    for cr in service.service_config.config_rules:
        if cr.WhichOneof('config_rule') != 'custom':
            continue
        val = json.loads(cr.custom.resource_value)
        if "sessionFilter" in val:
            info["origen"] = val["sessionFilter"].get("sourceIp")
            info["destino"] = val["sessionFilter"].get("dstAddress")
            break

    # Añadir timestamp
    unixtime = time.time()
    info["timeStamp"] = {
        "seconds": int(unixtime),
        "nanoseconds": int(unixtime % 1 * 1e9)
    }

    return info


def e2epathcomp_2_service(payload: dict) -> Service:
    """Crea un Service mínimo a partir de un payload E2E (origen, destino, bw, appInsId)"""
    service = Service()
    service.service_id.service_uuid.uuid = payload.get("appInsId", "tmp_uuid")
    service.service_id.context_id.context_uuid.uuid = "admin"
    service.service_type = ServiceTypeEnum.SERVICETYPE_L3NM
    service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED

    # Añadir ancho de banda
    if "fixedAllocation" in payload:
        capacity = Constraint_SLA_Capacity()
        capacity.capacity_gbps = float(payload["fixedAllocation"]) / 1e9
        constraint = Constraint()
        constraint.sla_capacity.CopyFrom(capacity)
        service.service_constraints.append(constraint)

    # Añadir sessionFilter
    if "origen" in payload and "destino" in payload:
        cr = ConfigRule()
        cr.action = ConfigActionEnum.CONFIGACTION_SET
        cr_custom = ConfigRule_Custom()
        cr_custom.resource_key = "/request"
        cr_custom.resource_value = json.dumps({
            "sessionFilter": {"sourceIp": payload["origen"], "dstAddress": payload["destino"]}
        })
        cr.custom.CopyFrom(cr_custom)
        service.service_config.config_rules.append(cr)

    return service


# ---------- Funciones auxiliares ----------

def format_grpc_to_json(grpc_reply):
    return jsonify(grpc_message_to_json(grpc_reply))


def grpc_context_id(context_uuid):
    return ContextId(**json_context_id(context_uuid))


def grpc_service_id(context_uuid, service_uuid):
    return ServiceId(**json_service_id(service_uuid, context_id=json_context_id(context_uuid)))
+30 −0
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.

from nbi.service.NbiApplication import NbiApplication
from .Resources import E2epathcomp, E2epathcompId

URL_PREFIX = '/restconf/e2epathcomp/v0'

def register_etsi_e2e_path_computation(nbi_app : NbiApplication):
    nbi_app.add_rest_api_resource(
        E2epathcompInfo,
        URL_PREFIX + '/bw_allocations',
        endpoint='etsi_bwm.bw_info'
    )
    nbi_app.add_rest_api_resource(
        E2epathcompInfoId,
        URL_PREFIX + '/bw_allocations/<path:allocationId>',
        endpoint='etsi_bwm.bw_info_id'
    )
Loading