Commit 31a95a52 authored by José Juan Pedreño Manresa's avatar José Juan Pedreño Manresa
Browse files

Implementation of NBI for IETF Network Slices

Addition of bindings for YANG "IETF Slice Service" data model parsing
Refactoring of subfolder "tools"
parent 9002ce2a
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -20,8 +20,8 @@ from common.proto.context_pb2 import SliceStatusEnum
from common.tools.context_queries.Slice import get_slice
from context.client.ContextClient import ContextClient
from slice.client.SliceClient import SliceClient
from .tools.Authentication import HTTP_AUTH
from .tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK, HTTP_SERVERERROR
from ..tools.Authentication import HTTP_AUTH
from ..tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK, HTTP_SERVERERROR

LOGGER = logging.getLogger(__name__)

+47 −14
Original line number Diff line number Diff line
@@ -15,9 +15,13 @@
import logging
from flask.json import jsonify
from flask_restful import Resource
from flask import request
from compute.service.rest_server.nbi_plugins.tools.Authentication import HTTP_AUTH
from compute.service.rest_server.nbi_plugins.tools.HttpStatusCodes import HTTP_OK
from common.proto.context_pb2 import SliceStatusEnum
from common.tools.context_queries.Slice import get_slice
from common.tools.grpc.Tools import grpc_message_to_json
from context.client.ContextClient import ContextClient
from slice.client.SliceClient import SliceClient
from ..tools.Authentication import HTTP_AUTH
from ..tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK, HTTP_SERVERERROR

LOGGER = logging.getLogger(__name__)

@@ -25,21 +29,50 @@ class NSS_Service(Resource):
    @HTTP_AUTH.login_required
    def get(self, slice_id : str):
        LOGGER.debug('GET Slice ID: {:s}'.format(str(slice_id)))
        LOGGER.debug('GET Request: {:s}'.format(str(request)))
        try:
            context_client = ContextClient()

        # TODO Return information and status about requested slice
            target = get_slice(context_client, slice_id, rw_copy=True)
            if target is None:
                raise Exception('Slice({:s}) not found in database'.format(str(slice_id)))

        response = jsonify({"message": "Requested info for slice {:s}".format(slice_id)})
        response.status_code = HTTP_OK
            if target.slice_id.slice_uuid.uuid != slice_id: # pylint: disable=no-member
                raise Exception('Slice retrieval failed. Wrong Slice Id was returned')

            slice_ready_status = SliceStatusEnum.SLICESTATUS_ACTIVE
            slice_status = target.slice_status.slice_status # pylint: disable=no-member
            response = jsonify(grpc_message_to_json(target))
            response.status_code = HTTP_OK if slice_status == slice_ready_status else HTTP_GATEWAYTIMEOUT

        except Exception as e: # pylint: disable=broad-except
            LOGGER.exception('Something went wrong Retrieving Slice({:s})'.format(str(slice_id)))
            response = jsonify({'error': str(e)})
            response.status_code = HTTP_SERVERERROR
        return response


    @HTTP_AUTH.login_required
    def delete(self, slice_id : str):
        LOGGER.debug('DELETE Slice ID: {:s}'.format(str(slice_id)))
        LOGGER.debug('DELETE Request: {:s}'.format(str(request)))
        try:
            context_client = ContextClient()
            target = get_slice(context_client, slice_id)

        # TODO Delete the requested slice

        response = jsonify({"message": "Deletion request for slice {:s}".format(slice_id)})
            response = jsonify({})
            response.status_code = HTTP_OK

            if target is None:
                LOGGER.warning('Slice({:s}) not found in database. Nothing done.'.format(str(slice_id)))
                response.status_code = HTTP_NOCONTENT
            else:
                if target.slice_id.slice_uuid.uuid != slice_id:  # pylint: disable=no-member
                    raise Exception('Slice retrieval failed. Wrong Slice Id was returned')
                slice_client = SliceClient()
                slice_client.DeleteSlice(target.slice_id)
                LOGGER.debug(f"Slice({slice_id}) successfully deleted")

        except Exception as e:  # pylint: disable=broad-except
            LOGGER.exception('Something went wrong Deleting Slice({:s})'.format(str(slice_id)))
            response = jsonify({'error': str(e)})
            response.status_code = HTTP_SERVERERROR
        return response
 No newline at end of file
+80 −13
Original line number Diff line number Diff line
@@ -11,34 +11,101 @@
# 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 ssl
from typing import Dict
from flask.json import jsonify
from flask_restful import Resource
from flask import request
from compute.service.rest_server.nbi_plugins.tools.Authentication import HTTP_AUTH
from compute.service.rest_server.nbi_plugins.tools.HttpStatusCodes import HTTP_BADREQUEST, HTTP_OK

from common.Constants import DEFAULT_CONTEXT_NAME
from common.proto.context_pb2 import Slice, SliceStatusEnum, EndPointId, Constraint
from common.tools.grpc.Tools import grpc_message_to_json
from ..tools.Authentication import HTTP_AUTH
from ..tools.HttpStatusCodes import HTTP_BADREQUEST, HTTP_OK, HTTP_CREATED, HTTP_SERVERERROR
from werkzeug.exceptions import UnsupportedMediaType

from slice.client.SliceClient import SliceClient
from .bindings import load_json_data
from .bindings.network_slice_services import NetworkSliceServices

LOGGER = logging.getLogger(__name__)

class NSS_Services(Resource):
    @HTTP_AUTH.login_required
    def get(self):    
        response = jsonify({"message": "All went well!"})
        response.status_code

        # TODO Return list of current network-slice-services
        return response

    @HTTP_AUTH.login_required
    def post(self):
        if not request.is_json: raise UnsupportedMediaType('JSON payload is required')
        request_data : Dict = request.json
        LOGGER.debug('POST Request: {:s}'.format(str(request_data)))
        if not request.is_json:
            raise UnsupportedMediaType('JSON payload is required')
        request_data = json.dumps(request.json)
        response = jsonify({})
        response.status_code = HTTP_CREATED

        slices: NetworkSliceServices = load_json_data(request_data, NetworkSliceServices)[0]
        for ietf_slice in slices.slice_service:
            slice_request: Slice = Slice()
            # Meta information
            # TODO implement name and owner based on "tags"
            slice_request.slice_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
            slice_request.slice_id.slice_uuid.uuid = ietf_slice.service_id()
            # TODO map with admin status of IETF Slice
            slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED

            list_endpoints = []
            for sdp in ietf_slice.sdps().sdp:
                endpoint = EndPointId()
                endpoint.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME
                endpoint.device_id.device_uuid.uuid = sdp.node_id()
                endpoint.endpoint_uuid.uuid = sdp.sdp_id()
                list_endpoints.append(endpoint)
            slice_request.slice_endpoint_ids.extend(list_endpoints)

            # TODO Map connectivity_groups and connectivity constructs to real connections
            LOGGER.debug(f"Connection groups detected: {len(ietf_slice.connection_groups().connection_group())}")
            list_constraints = []
            for cg in ietf_slice.connection_groups().connection_group:
                for cc in cg.connectivity_construct:
                    if cc.slo_sle_policy:
                        if cc.slo_sle_policy.custom:
                            with cc.slo_sle_policy.custom as slo:
                                for metric_bound in slo.service_slo_sle_policy().metric_bounds().metric_bound:
                                    if "service-slo-two-way-bandwidth" in str(metric_bound.metric_type()).casefold(): # TODO fix to two way!
                                        constraint = Constraint()
                                        metric_unit = metric_bound.metric_unit().casefold()
                                        capacity = float(metric_bound.value_description()) # Assuming capacity already in Gbps
                                        if metric_unit == "mbps":
                                            capacity /= 1E3
                                        elif metric_unit != "gbps":
                                            LOGGER.warning(f"Invalided metric unit ({metric_bound.metric_unit()}), must be Mbps or Gbps")
                                            response.status_code = HTTP_SERVERERROR
                                            return response
                                        constraint.sla_capacity.capacity_gbps = capacity
                                        list_constraints.append(constraint)

                                    elif "service-slo-one-way-delay" in str(metric_bound.metric_type()).casefold():
                                        if metric_bound.metric_unit().casefold() == "ms":
                                            latency = float(metric_bound.value_description())
                                        else:
                                            LOGGER.warning(f"Invalided metric unit ({metric_bound.metric_unit()}), must be \"ms\" ")
                                            response.status_code = HTTP_SERVERERROR
                                            return response
                                        constraint = Constraint()
                                        constraint.sla_latency.e2e_latency_ms = latency
                                        list_constraints.append(constraint)

        # TODO Parse network-slice-service request and cascade to Slice and Policy componentes
        response = jsonify({"message" : "POST message received correctly"})
        response.status_code = HTTP_OK
                                    elif "service-slo-availability":
                                        # TODO map to an availability number (or use a custom identity definition)
                                        constraint = Constraint()
                                        constraint.sla_availability.num_disjoint_paths = 2
                                        constraint.sla_availability.all_active = True
            slice_request.slice_constraints.extend(list_constraints)
            slice_client = SliceClient()
            slice_client.CreateSlice(slice_request)
            LOGGER.debug(grpc_message_to_json(slice_request)) # TODO remove
        return response
+521 −0

File added.

Preview size limit exceeded, changes collapsed.

+127 −0
Original line number Diff line number Diff line
from typing import Dict, Final

from .. import (
    YANGChoice, YANGChoiceCase, YANGContainer, YANGContainerMember,
    YANGLeafMember, YANGListItem, YANGListMember)


class NacmMeta(type):
    """
    Metaclass for YANG container handler.

    YANG name: nacm
    """
    from .groups import Groups
    from .rule_list import RuleList

    class yang_container_descriptor(
            YANGContainerMember):
        """
        YANG container descriptor class.

        YANG name: nacm
        """

        def __init__(self):
            super().__init__(Nacm)

        def __get__(self, instance, owner=None) -> (
                'NacmMeta.yang_container_descriptor'):
            return super().__get__(instance, owner)

        def __call__(self) -> 'Nacm':
            pass

        def __enter__(self) -> 'Nacm':
            pass


class Nacm(
        YANGContainer,
        metaclass=NacmMeta):
    """
    YANG container handler.

    YANG name: nacm
    """

    _yang_name: Final[str] = 'nacm'
    _yang_namespace: Final[str] = 'urn:ietf:params:xml:ns:yang:ietf-netconf-acm'
    _yang_module_name: Final[str] = 'ietf-netconf-acm'

    _yang_leaf_members: Final[Dict[str, YANGLeafMember]] = {

        'denied-data-writes': (
            denied_data_writes := YANGLeafMember(
                'denied-data-writes',
                'urn:ietf:params:xml:ns:yang:ietf-netconf-acm',
                'ietf-netconf-acm')),

        'exec-default': (
            exec_default := YANGLeafMember(
                'exec-default',
                'urn:ietf:params:xml:ns:yang:ietf-netconf-acm',
                'ietf-netconf-acm')),

        'denied-notifications': (
            denied_notifications := YANGLeafMember(
                'denied-notifications',
                'urn:ietf:params:xml:ns:yang:ietf-netconf-acm',
                'ietf-netconf-acm')),

        'enable-external-groups': (
            enable_external_groups := YANGLeafMember(
                'enable-external-groups',
                'urn:ietf:params:xml:ns:yang:ietf-netconf-acm',
                'ietf-netconf-acm')),

        'enable-nacm': (
            enable_nacm := YANGLeafMember(
                'enable-nacm',
                'urn:ietf:params:xml:ns:yang:ietf-netconf-acm',
                'ietf-netconf-acm')),

        'read-default': (
            read_default := YANGLeafMember(
                'read-default',
                'urn:ietf:params:xml:ns:yang:ietf-netconf-acm',
                'ietf-netconf-acm')),

        'denied-operations': (
            denied_operations := YANGLeafMember(
                'denied-operations',
                'urn:ietf:params:xml:ns:yang:ietf-netconf-acm',
                'ietf-netconf-acm')),

        'write-default': (
            write_default := YANGLeafMember(
                'write-default',
                'urn:ietf:params:xml:ns:yang:ietf-netconf-acm',
                'ietf-netconf-acm')),
    }

    _yang_container_members: Final[Dict[str, YANGContainerMember]] = {

        'groups': (
            groups := (  # YANGContainerMember(
                NacmMeta.
                Groups.
                yang_container_descriptor())),
    }

    _yang_list_members: Final[Dict[str, YANGListMember]] = {

        'rule-list': (
            rule_list := (  # YANGListMember(
                NacmMeta.
                RuleList.
                yang_list_descriptor())),
    }

    _yang_choices: Final[Dict[str, YANGChoice]] = None

    def __new__(cls, *args, **kwargs) -> 'Nacm':
        instance = super().__new__(cls)
        instance._yang_choices = {
        }
        return instance
Loading