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

Compute component:

- implemented delete of IETF L2VPN services/slices
- implemented diversity and availability constraints
parent e431d4d6
Loading
Loading
Loading
Loading
+26 −16
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ from common.Constants import DEFAULT_CONTEXT_UUID
from common.proto.context_pb2 import ServiceId, ServiceStatusEnum, SliceStatusEnum
from context.client.ContextClient import ContextClient
from service.client.ServiceClient import ServiceClient
from slice.client.SliceClient import SliceClient
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
@@ -32,11 +33,6 @@ class L2VPN_Service(Resource):
        LOGGER.debug('VPN_Id: {:s}'.format(str(vpn_id)))
        LOGGER.debug('Request: {:s}'.format(str(request)))

        # TODO: HACK ECOC'22, to be corrected
        response = jsonify({})
        response.status_code = HTTP_OK
        return response

        try:
            context_client = ContextClient()

@@ -60,7 +56,7 @@ class L2VPN_Service(Resource):

            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 VPN({:s})'.format(str(request)))
            LOGGER.exception('Something went wrong Retrieving VPN({:s})'.format(str(vpn_id)))
            response = jsonify({'error': str(e)})
            response.status_code = HTTP_SERVERERROR
        return response
@@ -70,18 +66,32 @@ 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

        try:
            context_client = ContextClient()

            target = get_service(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_client = ServiceClient()
            service_client.DeleteService(service_id_request)
                service_client.DeleteService(target.service_id)
                response = jsonify({})
                response.status_code = HTTP_NOCONTENT
                return response

            target = get_slice(context_client, vpn_id)
            if target is not 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_client = SliceClient()
                slice_client.DeleteSlice(target.slice_id)
                response = jsonify({})
                response.status_code = HTTP_NOCONTENT
                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 Deleting Service {:s}'.format(str(request)))
            LOGGER.exception('Something went wrong Deleting VPN({:s})'.format(str(vpn_id)))
            response = jsonify({'error': str(e)})
            response.status_code = HTTP_SERVERERROR
        return response
+78 −159
Original line number Diff line number Diff line
@@ -12,16 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import time, random
from ctypes import Union
import json, logging
from typing import Dict
import logging, random, time
from typing import Dict, Optional, Union
from flask import request
from flask.json import jsonify
from flask.wrappers import Response
from flask_restful import Resource
from werkzeug.exceptions import UnsupportedMediaType
from common.proto.context_pb2 import ConfigActionEnum, Service, Slice
from common.proto.context_pb2 import Service, Slice
from common.tools.grpc.ConfigRules import update_config_rule_custom
from common.tools.grpc.Constraints import (
    update_constraint_custom, update_constraint_endpoint_location, update_constraint_endpoint_priority,
    update_constraint_sla_availability)
from common.tools.grpc.EndPointIds import update_endpoint_ids
from common.tools.grpc.Tools import grpc_message_to_json_string
from context.client.ContextClient import ContextClient
from service.client.ServiceClient import ServiceClient
@@ -31,20 +34,32 @@ 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
from .Constants import (
    BEARER_MAPPINGS, DEFAULT_ADDRESS_FAMILIES, DEFAULT_BGP_AS, DEFAULT_BGP_ROUTE_TARGET, DEFAULT_MTU)

LOGGER = logging.getLogger(__name__)

def process_site_network_access(context_client : ContextClient, site_network_access : Dict) -> Service:
def process_site_network_access(context_client : ContextClient, site_id : str, site_network_access : Dict) -> Service:
    vpn_id = site_network_access['vpn-attachment']['vpn-id']
    cvlan_id = site_network_access['connection']['tagged-interface']['dot1q-vlan-tagged']['cvlan-id']
    encapsulation_type = site_network_access['connection']['encapsulation-type']
    cvlan_id = site_network_access['connection']['tagged-interface'][encapsulation_type]['cvlan-id']

    bearer_reference = site_network_access['bearer']['bearer-reference']
    access_priority = site_network_access.get('availability', {}).get('access-priority')
    single_active = site_network_access.get('availability', {}).get('single-active')
    all_active = site_network_access.get('availability', {}).get('all-active')

    access_priority : Optional[int] = site_network_access.get('availability', {}).get('access-priority')
    single_active   : bool = len(site_network_access.get('availability', {}).get('single-active', [])) > 0
    all_active      : bool = len(site_network_access.get('availability', {}).get('all-active', [])) > 0

    diversity_constraints = site_network_access.get('access-diversity', {}).get('constraints', {}).get('constraint', [])
    # TODO: manage targets of constraints, right now, only type of constraint is considered
    diversity_constraints = [constraint['constraint-type'] for constraint in diversity_constraints]
    raise_if_differs = True
    diversity_constraints = {
        constraint['constraint-type']:([
            target[0]
            for target in constraint['target'].items()
            if len(target[1]) == 1
        ][0], raise_if_differs)
        for constraint in diversity_constraints
    }

    mapping = BEARER_MAPPINGS.get(bearer_reference)
    if mapping is None:
@@ -57,157 +72,61 @@ def process_site_network_access(context_client : ContextClient, site_network_acc
    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)))

    # pylint: disable=no-member
    endpoint_ids = target.service_endpoint_ids if isinstance(target, Service) else target.slice_endpoint_ids

    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
    if isinstance(target, Service):
        endpoint_ids = target.service_endpoint_ids        # pylint: disable=no-member
        config_rules = target.service_config.config_rules # pylint: disable=no-member
        constraints  = target.service_constraints         # pylint: disable=no-member
    elif isinstance(target, Slice):
        endpoint_ids = target.slice_endpoint_ids        # pylint: disable=no-member
        config_rules = target.slice_config.config_rules # pylint: disable=no-member
        constraints  = target.slice_constraints         # pylint: disable=no-member
    else:
        # not found, add it
        endpoint_id = endpoint_ids.add()
        endpoint_id.device_id.device_uuid.uuid = device_uuid
        endpoint_id.endpoint_uuid.uuid = endpoint_uuid

    if isinstance(target, Slice): return target
        raise Exception('Target({:s}) not supported'.format(str(target.__class__.__name__)))

    for config_rule in target.service_config.config_rules:                  # pylint: disable=no-member
        if config_rule.WhichOneof('config_rule') != 'custom': continue
        if config_rule.custom.resource_key != '/settings': continue
        json_settings = json.loads(config_rule.custom.resource_value)
    endpoint_id = update_endpoint_ids(endpoint_ids, device_uuid, endpoint_uuid)

        if 'mtu' not in json_settings:                                      # missing, add it
            json_settings['mtu'] = DEFAULT_MTU
        elif json_settings['mtu'] != DEFAULT_MTU:                           # differs, raise exception
            msg = 'Specified MTU({:s}) differs from Service MTU({:s})'
            raise Exception(msg.format(str(json_settings['mtu']), str(DEFAULT_MTU)))

        if 'address_families' not in json_settings:                         # missing, add it
            json_settings['address_families'] = DEFAULT_ADDRESS_FAMILIES
        elif json_settings['address_families'] != DEFAULT_ADDRESS_FAMILIES: # differs, raise exception
            msg = 'Specified AddressFamilies({:s}) differs from Service AddressFamilies({:s})'
            raise Exception(msg.format(str(json_settings['address_families']), str(DEFAULT_ADDRESS_FAMILIES)))

        if 'bgp_as' not in json_settings:                                   # missing, add it
            json_settings['bgp_as'] = DEFAULT_BGP_AS
        elif json_settings['bgp_as'] != DEFAULT_BGP_AS:                     # differs, raise exception
            msg = 'Specified BgpAs({:s}) differs from Service BgpAs({:s})'
            raise Exception(msg.format(str(json_settings['bgp_as']), str(DEFAULT_BGP_AS)))

        if 'bgp_route_target' not in json_settings:                         # missing, add it
            json_settings['bgp_route_target'] = DEFAULT_BGP_ROUTE_TARGET
        elif json_settings['bgp_route_target'] != DEFAULT_BGP_ROUTE_TARGET: # differs, raise exception
            msg = 'Specified BgpRouteTarget({:s}) differs from Service BgpRouteTarget({:s})'
            raise Exception(msg.format(str(json_settings['bgp_route_target']), str(DEFAULT_BGP_ROUTE_TARGET)))

        config_rule.custom.resource_value = json.dumps(json_settings, sort_keys=True)
        break
    else:
        # not found, add it
        config_rule = target.service_config.config_rules.add()              # pylint: disable=no-member
        config_rule.action = ConfigActionEnum.CONFIGACTION_SET
        config_rule.custom.resource_key = '/settings'
        config_rule.custom.resource_value = json.dumps({
            'mtu'             : DEFAULT_MTU,
            'address_families': DEFAULT_ADDRESS_FAMILIES,
            'bgp_as'          : DEFAULT_BGP_AS,
            'bgp_route_target': DEFAULT_BGP_ROUTE_TARGET,
        }, sort_keys=True)
    service_settings_key = '/settings'
    update_config_rule_custom(config_rules, service_settings_key, {
        'mtu'             : (DEFAULT_MTU,              True),
        'address_families': (DEFAULT_ADDRESS_FAMILIES, True),
        'bgp_as'          : (DEFAULT_BGP_AS,           True),
        'bgp_route_target': (DEFAULT_BGP_ROUTE_TARGET, True),
    })

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

        if 'router_id' not in json_settings:                                # missing, add it
            json_settings['router_id'] = router_id
        elif json_settings['router_id'] != router_id:                       # differs, raise exception
            msg = 'Specified RouterId({:s}) differs from Service RouterId({:s})'
            raise Exception(msg.format(str(json_settings['router_id']), str(router_id)))

        if 'route_distinguisher' not in json_settings:                      # missing, add it
            json_settings['route_distinguisher'] = route_distinguisher
        elif json_settings['route_distinguisher'] != route_distinguisher:   # differs, raise exception
            msg = 'Specified RouteDistinguisher({:s}) differs from Service RouteDistinguisher({:s})'
            raise Exception(msg.format(str(json_settings['route_distinguisher']), str(route_distinguisher)))

        if 'sub_interface_index' not in json_settings:                      # missing, add it
            json_settings['sub_interface_index'] = sub_if_index
        elif json_settings['sub_interface_index'] != sub_if_index:   # differs, raise exception
            msg = 'Specified SubInterfaceIndex({:s}) differs from Service SubInterfaceIndex({:s})'
            raise Exception(msg.format(
                str(json_settings['sub_interface_index']), str(sub_if_index)))

        if 'vlan_id' not in json_settings:                                  # missing, add it
            json_settings['vlan_id'] = cvlan_id
        elif json_settings['vlan_id'] != cvlan_id:                          # differs, raise exception
            msg = 'Specified VLANId({:s}) differs from Service VLANId({:s})'
            raise Exception(msg.format(
                str(json_settings['vlan_id']), str(cvlan_id)))

        if address_ip is not None:
            if 'address_ip' not in json_settings:                               # missing, add it
                json_settings['address_ip'] = address_ip
            elif json_settings['address_ip'] != address_ip:                     # differs, raise exception
                msg = 'Specified AddressIP({:s}) differs from Service AddressIP({:s})'
                raise Exception(msg.format(
                    str(json_settings['address_ip']), str(address_ip)))

        if address_prefix is not None:
            if 'address_prefix' not in json_settings:                           # missing, add it
                json_settings['address_prefix'] = address_prefix
            elif json_settings['address_prefix'] != address_prefix:             # differs, raise exception
                msg = 'Specified AddressPrefix({:s}) differs from Service AddressPrefix({:s})'
                raise Exception(msg.format(
                    str(json_settings['address_prefix']), str(address_prefix)))

        if address_prefix is not None:
            if 'address_prefix' not in json_settings:                           # missing, add it
                json_settings['address_prefix'] = address_prefix
            elif json_settings['address_prefix'] != address_prefix:             # differs, raise exception
                msg = 'Specified AddressPrefix({:s}) differs from Service AddressPrefix({:s})'
                raise Exception(msg.format(
                    str(json_settings['address_prefix']), str(address_prefix)))

        config_rule.custom.resource_value = json.dumps(json_settings, sort_keys=True)
        break
    else:
        # not found, add it
        config_rule = target.service_config.config_rules.add()              # pylint: disable=no-member
        config_rule.action = ConfigActionEnum.CONFIGACTION_SET
        resource_value = {
            'router_id': router_id,
            'route_distinguisher': route_distinguisher,
            'sub_interface_index': sub_if_index,
            'vlan_id': cvlan_id,
            'address_ip': address_ip,
            'address_prefix': address_prefix,
    field_updates = {
        'router_id'          : (router_id,           True),
        'route_distinguisher': (route_distinguisher, True),
        'sub_interface_index': (sub_if_index,        True),
        'vlan_id'            : (cvlan_id,            True),
    }
        if access_priority is not None: resource_value['access_priority'] = access_priority
        if single_active is not None and len(single_active) > 0: resource_value['access_active'] = 'single'
        if all_active is not None and len(all_active) > 0: resource_value['access_active'] = 'all'
        config_rule.custom.resource_key = endpoint_settings_key
        config_rule.custom.resource_value = json.dumps(resource_value, sort_keys=True)
    if address_ip      is not None: field_updates['address_ip'     ] = (address_ip,      True)
    if address_prefix  is not None: field_updates['address_prefix' ] = (address_prefix,  True)
    update_config_rule_custom(config_rules, endpoint_settings_key, field_updates)

    for constraint in target.service_constraints:                           # pylint: disable=no-member
        if constraint.constraint_type == 'diversity' and len(diversity_constraints) > 0:
            constraint_value = set(json.loads(constraint.constraint_value))
            constraint_value.update(diversity_constraints)
            constraint.constraint_value = json.dumps(sorted(list(constraint_value)), sort_keys=True)
            break
    else:
        # not found, and there are diversity constraints, add them
    field_updates = {}
    if len(diversity_constraints) > 0:
            constraint = target.service_constraints.add()                   # pylint: disable=no-member
            constraint.constraint_type = 'diversity'
            constraint.constraint_value = json.dumps(sorted(list(diversity_constraints)), sort_keys=True)
        field_updates.update(diversity_constraints)
    update_constraint_custom(constraints, 'diversity', field_updates)

    update_constraint_endpoint_location(constraints, endpoint_id, region=site_id)
    if access_priority is not None: update_constraint_endpoint_priority(constraints, endpoint_id, access_priority)
    if single_active or all_active:
        # assume 1 disjoint path per endpoint/location included in service/slice
        location_endpoints = {}
        for constraint in constraints:
            if constraint.WhichOneof('constraint') != 'endpoint_location': continue
            str_endpoint_id = grpc_message_to_json_string(constraint.endpoint_location.endpoint_id)
            str_location_id = grpc_message_to_json_string(constraint.endpoint_location.location)
            location_endpoints.setdefault(str_location_id, set()).add(str_endpoint_id)
        num_endpoints_per_location = {len(endpoints) for endpoints in location_endpoints.values()}
        num_disjoint_paths = min(num_endpoints_per_location)
        update_constraint_sla_availability(constraints, num_disjoint_paths, all_active)

    return target

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

@@ -216,7 +135,7 @@ def process_list_site_network_access(

    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)
        sna_request = process_site_network_access(context_client, site_id, site_network_access)
        LOGGER.debug('sna_request = {:s}'.format(grpc_message_to_json_string(sna_request)))
        try:
            if isinstance(sna_request, Service):
@@ -230,7 +149,7 @@ def process_list_site_network_access(
            else:
                raise NotImplementedError('Support for Class({:s}) not implemented'.format(str(type(sna_request))))
        except Exception as e: # pylint: disable=broad-except
            msg = 'Something went wrong Updating Service {:s}'
            msg = 'Something went wrong Updating VPN {:s}'
            LOGGER.exception(msg.format(grpc_message_to_json_string(sna_request)))
            errors.append({'error': str(e)})
        time.sleep(random.random() / 10.0)
@@ -247,7 +166,7 @@ class L2VPN_SiteNetworkAccesses(Resource):
        context_client = ContextClient()
        service_client = ServiceClient()
        slice_client = SliceClient()
        return process_list_site_network_access(context_client, service_client, slice_client, request.json)
        return process_list_site_network_access(context_client, service_client, slice_client, site_id, request.json)

    @HTTP_AUTH.login_required
    def put(self, site_id : str):
@@ -256,4 +175,4 @@ class L2VPN_SiteNetworkAccesses(Resource):
        context_client = ContextClient()
        service_client = ServiceClient()
        slice_client = SliceClient()
        return process_list_site_network_access(context_client, service_client, slice_client, request.json)
        return process_list_site_network_access(context_client, service_client, slice_client, site_id, request.json)