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

Integrated basic slicing skeleton.

Compute:
- added support for Slice creation when VPLS service is requested instead of VPWS

Context:
- minor improvements in client class logging
- added slice events to EventsCollector
- minor bug fixes in SliceModel

Interdomain:
- Implemented basic workflow

Monitoring:
- fixed requirements versions

Service:
- minor improvements in client and server logging

Slice:
- Implemented basic workflow

OECC/PSC'22 functional test:
- updated test objects
- updated deployment script
- updated exposed services
parent a2003459
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ COPY common/. common
COPY compute/. compute
COPY context/. context
COPY service/. service
COPY slice/. slice

# Start compute service
ENTRYPOINT ["python", "-m", "compute.service"]
+26 −0
Original line number Diff line number Diff line
@@ -25,4 +25,30 @@ BEARER_MAPPINGS = {
    'R2-EMU:13/2/1': ('R2-EMU', '13/2/1', '12.12.12.1', '65000:120', 450, '3.4.2.1', 24),
    'R3-INF:13/2/1': ('R3-INF', '13/2/1', '20.20.20.1', '65000:200', 500, '3.3.1.1', 24),
    'R4-EMU:13/2/1': ('R4-EMU', '13/2/1', '22.22.22.1', '65000:220', 550, '3.4.1.1', 24),

    'R1@D1:3/1': ('R1@D1', '3/1', '10.0.1.1', '65001:101', 100, '1.1.3.1', 24),
    'R1@D1:3/2': ('R1@D1', '3/2', '10.0.1.1', '65001:101', 100, '1.1.3.2', 24),
    'R1@D1:3/3': ('R1@D1', '3/3', '10.0.1.1', '65001:101', 100, '1.1.3.3', 24),
    'R2@D1:3/1': ('R2@D1', '3/1', '10.0.1.2', '65001:102', 100, '1.2.3.1', 24),
    'R2@D1:3/2': ('R2@D1', '3/2', '10.0.1.2', '65001:102', 100, '1.2.3.2', 24),
    'R2@D1:3/3': ('R2@D1', '3/3', '10.0.1.2', '65001:102', 100, '1.2.3.3', 24),
    'R3@D1:3/1': ('R3@D1', '3/1', '10.0.1.3', '65001:103', 100, '1.3.3.1', 24),
    'R3@D1:3/2': ('R3@D1', '3/2', '10.0.1.3', '65001:103', 100, '1.3.3.2', 24),
    'R3@D1:3/3': ('R3@D1', '3/3', '10.0.1.3', '65001:103', 100, '1.3.3.3', 24),
    'R4@D1:3/1': ('R4@D1', '3/1', '10.0.1.4', '65001:104', 100, '1.4.3.1', 24),
    'R4@D1:3/2': ('R4@D1', '3/2', '10.0.1.4', '65001:104', 100, '1.4.3.2', 24),
    'R4@D1:3/3': ('R4@D1', '3/3', '10.0.1.4', '65001:104', 100, '1.4.3.3', 24),

    'R1@D2:3/1': ('R1@D2', '3/1', '10.0.2.1', '65002:101', 100, '2.1.3.1', 24),
    'R1@D2:3/2': ('R1@D2', '3/2', '10.0.2.1', '65002:101', 100, '2.1.3.2', 24),
    'R1@D2:3/3': ('R1@D2', '3/3', '10.0.2.1', '65002:101', 100, '2.1.3.3', 24),
    'R2@D2:3/1': ('R2@D2', '3/1', '10.0.2.2', '65002:102', 100, '2.2.3.1', 24),
    'R2@D2:3/2': ('R2@D2', '3/2', '10.0.2.2', '65002:102', 100, '2.2.3.2', 24),
    'R2@D2:3/3': ('R2@D2', '3/3', '10.0.2.2', '65002:102', 100, '2.2.3.3', 24),
    'R3@D2:3/1': ('R3@D2', '3/1', '10.0.2.3', '65002:103', 100, '2.3.3.1', 24),
    'R3@D2:3/2': ('R3@D2', '3/2', '10.0.2.3', '65002:103', 100, '2.3.3.2', 24),
    'R3@D2:3/3': ('R3@D2', '3/3', '10.0.2.3', '65002:103', 100, '2.3.3.3', 24),
    'R4@D2:3/1': ('R4@D2', '3/1', '10.0.2.4', '65002:104', 100, '2.4.3.1', 24),
    'R4@D2:3/2': ('R4@D2', '3/2', '10.0.2.4', '65002:104', 100, '2.4.3.2', 24),
    'R4@D2:3/3': ('R4@D2', '3/3', '10.0.2.4', '65002:104', 100, '2.4.3.3', 24),
}
+24 −13
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from ctypes import Union
import logging
from flask import request
from flask.json import jsonify
@@ -19,10 +20,11 @@ from flask_restful import Resource
from common.Constants import DEFAULT_CONTEXT_UUID
from common.Settings import get_setting
from context.client.ContextClient import ContextClient
from context.proto.context_pb2 import ServiceId
from context.proto.context_pb2 import Service, ServiceId, Slice, SliceStatusEnum
from service.client.ServiceClient import ServiceClient
from service.proto.context_pb2 import ServiceStatusEnum
from .tools.Authentication import HTTP_AUTH
from .tools.ContextMethods import get_service, get_slice
from .tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK, HTTP_SERVERERROR

LOGGER = logging.getLogger(__name__)
@@ -40,21 +42,30 @@ class L2VPN_Service(Resource):
        LOGGER.debug('VPN_Id: {:s}'.format(str(vpn_id)))
        LOGGER.debug('Request: {:s}'.format(str(request)))

        # pylint: disable=no-member
        service_id_request = ServiceId()
        service_id_request.context_id.context_uuid.uuid = DEFAULT_CONTEXT_UUID
        service_id_request.service_uuid.uuid = vpn_id
        response = jsonify({})

        try:
            service_reply = self.context_client.GetService(service_id_request)
            if service_reply.service_id != service_id_request: # pylint: disable=no-member
            target = get_service(self.context_client, vpn_id)
            if target is not None:
                if target.service_id.service_uuid.uuid != vpn_id: # pylint: disable=no-member
                    raise Exception('Service retrieval failed. Wrong Service Id was returned')
                service_ready_status = ServiceStatusEnum.SERVICESTATUS_ACTIVE
            service_status = service_reply.service_status.service_status
            response = jsonify({})
                service_status = target.service_status.service_status
                response.status_code = HTTP_OK if service_status == service_ready_status else HTTP_GATEWAYTIMEOUT
                return response

            target = get_slice(self.context_client, vpn_id)
            if target is None:
                if target.slice_id.slice_uuid.uuid != vpn_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
                response.status_code = HTTP_OK if slice_status == slice_ready_status else HTTP_GATEWAYTIMEOUT
                return response

            raise Exception('VPN({:s}) not found in database'.format(str(vpn_id)))
        except Exception as e: # pylint: disable=broad-except
            LOGGER.exception('Something went wrong Retrieving Service {:s}'.format(str(request)))
            LOGGER.exception('Something went wrong Retrieving VPN({:s})'.format(str(request)))
            response = jsonify({'error': str(e)})
            response.status_code = HTTP_SERVERERROR
        return response
+26 −10
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ from common.Constants import DEFAULT_CONTEXT_UUID
from common.Settings import get_setting
from service.client.ServiceClient import ServiceClient
from service.proto.context_pb2 import Service, ServiceStatusEnum, ServiceTypeEnum
from slice.client.SliceClient import SliceClient
from slice.proto.context_pb2 import SliceStatusEnum, Slice
from .schemas.vpn_service import SCHEMA_VPN_SERVICE
from .tools.Authentication import HTTP_AUTH
from .tools.HttpStatusCodes import HTTP_CREATED, HTTP_SERVERERROR
@@ -34,6 +36,8 @@ class L2VPN_Services(Resource):
        super().__init__()
        self.service_client = ServiceClient(
            get_setting('SERVICESERVICE_SERVICE_HOST'), get_setting('SERVICESERVICE_SERVICE_PORT_GRPC'))
        self.slice_client = SliceClient(
            get_setting('SLICESERVICE_SERVICE_HOST'), get_setting('SLICESERVICE_SERVICE_PORT_GRPC'))

    @HTTP_AUTH.login_required
    def get(self):
@@ -48,6 +52,9 @@ class L2VPN_Services(Resource):

        vpn_services : List[Dict] = request_data['ietf-l2vpn-svc:vpn-service']
        for vpn_service in vpn_services:
            try:
                vpn_service_type = vpn_service['vpn-svc-type']
                if vpn_service_type == 'vpws':
                    # pylint: disable=no-member
                    service_request = Service()
                    service_request.service_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_UUID
@@ -55,10 +62,19 @@ class L2VPN_Services(Resource):
                    service_request.service_type = ServiceTypeEnum.SERVICETYPE_L3NM
                    service_request.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED

            try:
                    service_reply = self.service_client.CreateService(service_request)
                    if service_reply != service_request.service_id: # pylint: disable=no-member
                        raise Exception('Service creation failed. Wrong Service Id was returned')
                elif vpn_service_type == 'vpls':
                    # pylint: disable=no-member
                    slice_request = Slice()
                    slice_request.slice_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_UUID
                    slice_request.slice_id.slice_uuid.uuid = vpn_service['vpn-id']
                    slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED

                    slice_reply = self.slice_client.CreateSlice(slice_request)
                    if slice_reply != slice_request.slice_id: # pylint: disable=no-member
                        raise Exception('Slice creation failed. Wrong Slice Id was returned')

                response = jsonify({})
                response.status_code = HTTP_CREATED
+42 −25
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from ctypes import Union
import json, logging
from typing import Dict
from flask import request
@@ -19,14 +20,15 @@ from flask.json import jsonify
from flask.wrappers import Response
from flask_restful import Resource
from werkzeug.exceptions import UnsupportedMediaType
from common.Constants import DEFAULT_CONTEXT_UUID
from common.Settings import get_setting
from common.tools.grpc.Tools import grpc_message_to_json_string
from context.client.ContextClient import ContextClient
from context.proto.context_pb2 import ConfigActionEnum, Service, ServiceId
from context.proto.context_pb2 import ConfigActionEnum, Service, Slice
from service.client.ServiceClient import ServiceClient
from slice.client.SliceClient import SliceClient
from .schemas.site_network_access import SCHEMA_SITE_NETWORK_ACCESS
from .tools.Authentication import HTTP_AUTH
from .tools.ContextMethods import get_service, get_slice
from .tools.HttpStatusCodes import HTTP_NOCONTENT, HTTP_SERVERERROR
from .tools.Validator import validate_message
from .Constants import BEARER_MAPPINGS, DEFAULT_ADDRESS_FAMILIES, DEFAULT_BGP_AS, DEFAULT_BGP_ROUTE_TARGET, DEFAULT_MTU
@@ -44,26 +46,27 @@ def process_site_network_access(context_client : ContextClient, site_network_acc
        raise Exception(msg.format(str(bearer_reference)))
    device_uuid,endpoint_uuid,router_id,route_distinguisher,sub_if_index,address_ip,address_prefix = mapping

    # pylint: disable=no-member
    service_id = ServiceId()
    service_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_UUID
    service_id.service_uuid.uuid = vpn_id
    target : Union[Service, Slice, None] = None
    if target is None: target = get_service(context_client, vpn_id)
    if target is None: target = get_slice  (context_client, vpn_id)
    if target is None: raise Exception('VPN({:s}) not found in database'.format(str(vpn_id)))

    service_readonly = context_client.GetService(service_id)
    service = Service()
    service.CopyFrom(service_readonly)
    # pylint: disable=no-member
    endpoint_ids = target.service_endpoint_ids if isinstance(target, Service) else target.slice_endpoint_ids

    for endpoint_id in service.service_endpoint_ids:                        # pylint: disable=no-member
    for endpoint_id in endpoint_ids:
        if endpoint_id.device_id.device_uuid.uuid != device_uuid: continue
        if endpoint_id.endpoint_uuid.uuid != endpoint_uuid: continue
        break   # found, do nothing
    else:
        # not found, add it
        endpoint_id = service.service_endpoint_ids.add()                    # pylint: disable=no-member
        endpoint_id = endpoint_ids.add()
        endpoint_id.device_id.device_uuid.uuid = device_uuid
        endpoint_id.endpoint_uuid.uuid = endpoint_uuid

    for config_rule in service.service_config.config_rules:                 # pylint: disable=no-member
    if isinstance(target, Slice): return target

    for config_rule in target.service_config.config_rules:                  # pylint: disable=no-member
        if config_rule.resource_key != '/settings': continue
        json_settings = json.loads(config_rule.resource_value)

@@ -95,7 +98,7 @@ def process_site_network_access(context_client : ContextClient, site_network_acc
        break
    else:
        # not found, add it
        config_rule = service.service_config.config_rules.add()             # pylint: disable=no-member
        config_rule = target.service_config.config_rules.add()              # pylint: disable=no-member
        config_rule.action = ConfigActionEnum.CONFIGACTION_SET
        config_rule.resource_key = '/settings'
        config_rule.resource_value = json.dumps({
@@ -106,7 +109,7 @@ def process_site_network_access(context_client : ContextClient, site_network_acc
        }, sort_keys=True)

    endpoint_settings_key = '/device[{:s}]/endpoint[{:s}]/settings'.format(device_uuid, endpoint_uuid)
    for config_rule in service.service_config.config_rules:                 # pylint: disable=no-member
    for config_rule in target.service_config.config_rules:                  # pylint: disable=no-member
        if config_rule.resource_key != endpoint_settings_key: continue
        json_settings = json.loads(config_rule.resource_value)

@@ -154,7 +157,7 @@ def process_site_network_access(context_client : ContextClient, site_network_acc
        break
    else:
        # not found, add it
        config_rule = service.service_config.config_rules.add()             # pylint: disable=no-member
        config_rule = target.service_config.config_rules.add()              # pylint: disable=no-member
        config_rule.action = ConfigActionEnum.CONFIGACTION_SET
        config_rule.resource_key = endpoint_settings_key
        config_rule.resource_value = json.dumps({
@@ -166,24 +169,34 @@ def process_site_network_access(context_client : ContextClient, site_network_acc
            'address_prefix': address_prefix,
        }, sort_keys=True)

    return service
    return target

def process_list_site_network_access(
    context_client : ContextClient, service_client : ServiceClient, request_data : Dict) -> Response:
        context_client : ContextClient, service_client : ServiceClient, slice_client : SliceClient,
        request_data : Dict
    ) -> Response:

    LOGGER.debug('Request: {:s}'.format(str(request_data)))
    validate_message(SCHEMA_SITE_NETWORK_ACCESS, request_data)

    errors = []
    for site_network_access in request_data['ietf-l2vpn-svc:site-network-access']:
        sna_request = process_site_network_access(context_client, site_network_access)
        LOGGER.debug('sna_request = {:s}'.format(grpc_message_to_json_string(sna_request)))
        try:
            service_request = process_site_network_access(context_client, site_network_access)
            LOGGER.debug('service_request = {:s}'.format(grpc_message_to_json_string(service_request)))
            service_reply = service_client.UpdateService(service_request)
            if service_reply != service_request.service_id: # pylint: disable=no-member
            if isinstance(sna_request, Service):
                sna_reply = service_client.UpdateService(sna_request)
                if sna_reply != sna_request.service_id: # pylint: disable=no-member
                    raise Exception('Service update failed. Wrong Service Id was returned')
            elif isinstance(sna_request, Slice):
                sna_reply = slice_client.UpdateSlice(sna_request)
                if sna_reply != sna_request.slice_id: # pylint: disable=no-member
                    raise Exception('Slice update failed. Wrong Slice Id was returned')
            else:
                raise NotImplementedError('Support for Class({:s}) not implemented'.format(str(type(sna_request))))
        except Exception as e: # pylint: disable=broad-except
            LOGGER.exception('Something went wrong Updating Service {:s}'.format(str(request)))
            msg = 'Something went wrong Updating Service {:s}'
            LOGGER.exception(msg.format(grpc_message_to_json_string(sna_request)))
            errors.append({'error': str(e)})

    response = jsonify(errors)
@@ -197,15 +210,19 @@ class L2VPN_SiteNetworkAccesses(Resource):
            get_setting('CONTEXTSERVICE_SERVICE_HOST'), get_setting('CONTEXTSERVICE_SERVICE_PORT_GRPC'))
        self.service_client = ServiceClient(
            get_setting('SERVICESERVICE_SERVICE_HOST'), get_setting('SERVICESERVICE_SERVICE_PORT_GRPC'))
        self.slice_client = SliceClient(
            get_setting('SLICESERVICE_SERVICE_HOST'), get_setting('SLICESERVICE_SERVICE_PORT_GRPC'))

    @HTTP_AUTH.login_required
    def post(self, site_id : str):
        if not request.is_json: raise UnsupportedMediaType('JSON payload is required')
        LOGGER.debug('Site_Id: {:s}'.format(str(site_id)))
        return process_list_site_network_access(self.context_client, self.service_client, request.json)
        return process_list_site_network_access(
            self.context_client, self.service_client, self.slice_client, request.json)

    @HTTP_AUTH.login_required
    def put(self, site_id : str):
        if not request.is_json: raise UnsupportedMediaType('JSON payload is required')
        LOGGER.debug('Site_Id: {:s}'.format(str(site_id)))
        return process_list_site_network_access(self.context_client, self.service_client, request.json)
        return process_list_site_network_access(
            self.context_client, self.service_client, self.slice_client, request.json)
Loading