Commit c5a5d8ad authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Merge branch 'feat/compute-etsi_mec_015' into 'develop'

Resolve "(CTTC) Implement ETSI MEC Bandwidth Management API in NBI component"

See merge request !120
parents eaab9562 0d3549fe
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ from common.Settings import (
from .ComputeService import ComputeService
from .rest_server.RestServer import RestServer
from .rest_server.nbi_plugins.debug_api import register_debug_api
from .rest_server.nbi_plugins.etsi_bwm import register_etsi_bwm_api
from .rest_server.nbi_plugins.ietf_l2vpn import register_ietf_l2vpn
from .rest_server.nbi_plugins.ietf_network_slice import register_ietf_nss

@@ -59,9 +60,10 @@ def main():
    grpc_service.start()

    rest_server = RestServer()
    register_debug_api(rest_server)
    register_etsi_bwm_api(rest_server)
    register_ietf_l2vpn(rest_server)  # Registering L2VPN entrypoint
    register_ietf_nss(rest_server)  # Registering NSS entrypoint
    register_debug_api(rest_server)
    rest_server.start()

    # Wait for Ctrl+C or termination signal
+75 −0
Original line number Diff line number Diff line
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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
from common.Constants import DEFAULT_CONTEXT_NAME
from flask_restful import Resource, request
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, bwInfo_2_service, service_2_bwInfo)



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


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

    def post(self):
        bwinfo = request.get_json()
        service = bwInfo_2_service(self.client, bwinfo)
        stripped_service = copy.deepcopy(service)

        stripped_service.ClearField('service_endpoint_ids')
        stripped_service.ClearField('service_constraints')
        stripped_service.ClearField('service_config')

        response = format_grpc_to_json(self.service_client.CreateService(stripped_service))
        response = format_grpc_to_json(self.service_client.UpdateService(service))

        return response


class BwInfoId(_Resource):

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

    def put(self, allocationId: str):
        json_data = request.get_json()
        service = bwInfo_2_service(self.client, json_data)
        response = self.service_client.UpdateService(service)
        return format_grpc_to_json(response)

    def patch(self, allocationId: str):
        json_data = request.get_json()
        if not 'appInsId' in json_data:
            json_data['appInsId'] = allocationId
        service = bwInfo_2_service(self.client, json_data)
        response = self.service_client.UpdateService(service)
        return format_grpc_to_json(response)

    def delete(self, allocationId: str):
        self.service_client.DeleteService(grpc_service_id(DEFAULT_CONTEXT_NAME, allocationId))
        return
+114 −0
Original line number Diff line number Diff line
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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 time
from flask.json import jsonify
from common.proto.context_pb2 import ContextId, Empty, EndPointId, ServiceId, 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__)


def service_2_bwInfo(service: Service) -> dict:
    response = {}
    # allocationDirection = '??' # String: 00 = Downlink (towards the UE); 01 = Uplink (towards the application/session); 10 = Symmetrical
    response['appInsId'] = service.service_id.context_id.context_uuid.uuid # String: Application instance identifier
    for constraint in service.service_constraints:
        if constraint.WhichOneof('constraint') == 'sla_capacity':
            response['fixedAllocation'] = str(constraint.sla_capacity.capacity_gbps*1000) # String: Size of requested fixed BW allocation in [bps]
            break


    for config_rule in service.service_config.config_rules:
        for key in ['allocationDirection', 'fixedBWPriority', 'requestType', 'sourceIp', 'sourcePort', 'dstPort', 'protocol', 'sessionFilter']:
            if config_rule.custom.resource_key == key:
                if key != 'sessionFilter':
                    response[key] = config_rule.custom.resource_value
                else:
                    response[key] = json.loads(config_rule.custom.resource_value)

    
    unixtime = time.time()
    response['timeStamp'] = { # Time stamp to indicate when the corresponding information elements are sent
        "seconds": int(unixtime),
        "nanoseconds": int(unixtime%1*1e9)
    }

    return response

def bwInfo_2_service(client, bwInfo: dict) -> Service:
    service = Service()

    for key in ['allocationDirection', 'fixedBWPriority', 'requestType', 'timeStamp', 'sessionFilter']:
        if key not in bwInfo:
            continue
        config_rule = ConfigRule()
        config_rule.action = ConfigActionEnum.CONFIGACTION_SET
        config_rule_custom = ConfigRule_Custom()
        config_rule_custom.resource_key  = key
        if key != 'sessionFilter':
            config_rule_custom.resource_value  = str(bwInfo[key])
        else:
            config_rule_custom.resource_value  = json.dumps(bwInfo[key])
        config_rule.custom.CopyFrom(config_rule_custom)
        service.service_config.config_rules.append(config_rule)

    if 'sessionFilter' in bwInfo:
        a_ip = bwInfo['sessionFilter'][0]['sourceIp']
        z_ip = bwInfo['sessionFilter'][0]['dstAddress']

        devices = client.ListDevices(Empty()).devices
        for device in devices:
            for cr in device.device_config.config_rules:
                if cr.WhichOneof('config_rule') == 'custom' and cr.custom.resource_key == '_connect/settings':
                    for ep in json.loads(cr.custom.resource_value)['endpoints']:
                        if 'ip' in ep and (ep['ip'] == a_ip or ep['ip'] == z_ip):
                            ep_id = EndPointId()
                            ep_id.endpoint_uuid.uuid = ep['uuid']
                            ep_id.device_id.device_uuid.uuid = device.device_id.device_uuid.uuid
                            service.service_endpoint_ids.append(ep_id)
        
        if len(service.service_endpoint_ids) < 2:
            LOGGER.error('No endpoints matched')
            return None

    service.service_type = ServiceTypeEnum.SERVICETYPE_L3NM

    if 'appInsId' in bwInfo:
        service.service_id.service_uuid.uuid = bwInfo['appInsId']
        service.service_id.context_id.context_uuid.uuid = 'admin'
        service.name = bwInfo['appInsId']

    if 'fixedAllocation' in bwInfo:
        capacity = Constraint_SLA_Capacity()
        capacity.capacity_gbps = float(bwInfo['fixedAllocation'])
        constraint = Constraint()
        constraint.sla_capacity.CopyFrom(capacity)
        service.service_constraints.append(constraint)

    return service


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)))
+29 −0
Original line number Diff line number Diff line
# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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 compute.service.rest_server.RestServer import RestServer
from .Resources import BwInfo, BwInfoId

URL_PREFIX = '/bwm/v1'

# Use 'path' type since some identifiers might contain char '/' and Flask is unable to recognize them in 'string' type.
RESOURCES = [
    # (endpoint_name, resource_class, resource_url)
    ('api.bw_info',         BwInfo,     '/bw_allocations'),
    ('api.bw_info_id',      BwInfoId,   '/bw_allocations/<path:allocationId>'),
]

def register_etsi_bwm_api(rest_server : RestServer):
    for endpoint_name, resource_class, resource_url in RESOURCES:
        rest_server.add_resource(resource_class, URL_PREFIX + resource_url, endpoint=endpoint_name)
+81 −0
Original line number Diff line number Diff line
-----------------------GET-----------------------

curl --request GET \
  --url http://10.1.7.203:80/restconf/bwm/v1/bw_allocations


-----------------------POST-----------------------
curl --request POST \
  --url http://10.1.7.203:80/restconf/bwm/v1/bw_allocations \
  --header 'Content-Type: application/json' \
  --data '{
  "allocationDirection": "string",
  "appInsId": "service_uuid",
  "fixedAllocation": "123",
  "fixedBWPriority": "SEE_DESCRIPTION",
  "requestType": 0,
  "sessionFilter": [
    {
      "dstAddress": "192.168.3.2",
      "dstPort": [
        "b"
      ],
      "protocol": "string",
      "sourceIp": "192.168.1.2",
      "sourcePort": [
        "a"
      ]
    }
  ],
  "timeStamp": {
    "nanoSeconds": 1,
    "seconds": 1
  }
}'


-----------------------GET2-----------------------
curl --request GET \
  --url http://10.1.7.203:80/restconf/bwm/v1/bw_allocations/service_uuid

-----------------------PUT-----------------------
  curl --request PUT \
  --url http://10.1.7.203:80/restconf/bwm/v1/bw_allocations/service_uuid \
  --header 'Content-Type: application/json' \
  --data '{
  "allocationDirection": "string",
  "appInsId": "service_uuid",
  "fixedAllocation": "123",
  "fixedBWPriority": "efefe",
  "requestType": 0,
  "sessionFilter": [
    {
      "dstAddress": "192.168.3.2",
      "dstPort": [
        "b"
      ],
      "protocol": "string",
      "sourceIp": "192.168.1.2",
      "sourcePort": [
        "a"
      ]
    }
  ],
  "timeStamp": {
    "nanoSeconds": 1,
    "seconds": 1
  }
}'

-----------------------PATCH-----------------------
curl --request PATCH \
  --url http://10.1.7.203:80/restconf/bwm/v1/bw_allocations/service_uuid \
  --header 'Content-Type: application/json' \
  --data '{
  "fixedBWPriority": "uuuuuuuuuuuuuu"
}'


-----------------------DELETE-----------------------
curl --request DELETE \
  --url http://10.1.7.203:80/restconf/bwm/v1/bw_allocations/service_uuid
 No newline at end of file