Skip to content
Snippets Groups Projects
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
No related branches found
No related tags found
2 merge requests!54Release 2.0.0,!4Compute component:
...@@ -20,6 +20,7 @@ from common.Constants import DEFAULT_CONTEXT_UUID ...@@ -20,6 +20,7 @@ from common.Constants import DEFAULT_CONTEXT_UUID
from common.proto.context_pb2 import ServiceId, ServiceStatusEnum, SliceStatusEnum from common.proto.context_pb2 import ServiceId, ServiceStatusEnum, SliceStatusEnum
from context.client.ContextClient import ContextClient from context.client.ContextClient import ContextClient
from service.client.ServiceClient import ServiceClient from service.client.ServiceClient import ServiceClient
from slice.client.SliceClient import SliceClient
from .tools.Authentication import HTTP_AUTH from .tools.Authentication import HTTP_AUTH
from .tools.ContextMethods import get_service, get_slice from .tools.ContextMethods import get_service, get_slice
from .tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK, HTTP_SERVERERROR from .tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK, HTTP_SERVERERROR
...@@ -32,11 +33,6 @@ class L2VPN_Service(Resource): ...@@ -32,11 +33,6 @@ class L2VPN_Service(Resource):
LOGGER.debug('VPN_Id: {:s}'.format(str(vpn_id))) LOGGER.debug('VPN_Id: {:s}'.format(str(vpn_id)))
LOGGER.debug('Request: {:s}'.format(str(request))) LOGGER.debug('Request: {:s}'.format(str(request)))
# TODO: HACK ECOC'22, to be corrected
response = jsonify({})
response.status_code = HTTP_OK
return response
try: try:
context_client = ContextClient() context_client = ContextClient()
...@@ -60,7 +56,7 @@ class L2VPN_Service(Resource): ...@@ -60,7 +56,7 @@ class L2VPN_Service(Resource):
raise Exception('VPN({:s}) not found in database'.format(str(vpn_id))) raise Exception('VPN({:s}) not found in database'.format(str(vpn_id)))
except Exception as e: # pylint: disable=broad-except 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 = jsonify({'error': str(e)})
response.status_code = HTTP_SERVERERROR response.status_code = HTTP_SERVERERROR
return response return response
...@@ -70,18 +66,32 @@ class L2VPN_Service(Resource): ...@@ -70,18 +66,32 @@ class L2VPN_Service(Resource):
LOGGER.debug('VPN_Id: {:s}'.format(str(vpn_id))) LOGGER.debug('VPN_Id: {:s}'.format(str(vpn_id)))
LOGGER.debug('Request: {:s}'.format(str(request))) 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: try:
service_client = ServiceClient() context_client = ContextClient()
service_client.DeleteService(service_id_request)
response = jsonify({}) target = get_service(context_client, vpn_id)
response.status_code = HTTP_NOCONTENT 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(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 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 = jsonify({'error': str(e)})
response.status_code = HTTP_SERVERERROR response.status_code = HTTP_SERVERERROR
return response return response
...@@ -12,16 +12,19 @@ ...@@ -12,16 +12,19 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import time, random import logging, random, time
from ctypes import Union from typing import Dict, Optional, Union
import json, logging
from typing import Dict
from flask import request from flask import request
from flask.json import jsonify from flask.json import jsonify
from flask.wrappers import Response from flask.wrappers import Response
from flask_restful import Resource from flask_restful import Resource
from werkzeug.exceptions import UnsupportedMediaType 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 common.tools.grpc.Tools import grpc_message_to_json_string
from context.client.ContextClient import ContextClient from context.client.ContextClient import ContextClient
from service.client.ServiceClient import ServiceClient from service.client.ServiceClient import ServiceClient
...@@ -31,20 +34,32 @@ from .tools.Authentication import HTTP_AUTH ...@@ -31,20 +34,32 @@ from .tools.Authentication import HTTP_AUTH
from .tools.ContextMethods import get_service, get_slice from .tools.ContextMethods import get_service, get_slice
from .tools.HttpStatusCodes import HTTP_NOCONTENT, HTTP_SERVERERROR from .tools.HttpStatusCodes import HTTP_NOCONTENT, HTTP_SERVERERROR
from .tools.Validator import validate_message 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__) 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'] 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'] 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') access_priority : Optional[int] = site_network_access.get('availability', {}).get('access-priority')
all_active = site_network_access.get('availability', {}).get('all-active') 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', []) 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 raise_if_differs = True
diversity_constraints = [constraint['constraint-type'] for constraint in diversity_constraints] 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) mapping = BEARER_MAPPINGS.get(bearer_reference)
if mapping is None: if mapping is None:
...@@ -57,157 +72,61 @@ def process_site_network_access(context_client : ContextClient, site_network_acc ...@@ -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: target = get_slice (context_client, vpn_id)
if target is None: raise Exception('VPN({:s}) not found in database'.format(str(vpn_id))) if target is None: raise Exception('VPN({:s}) not found in database'.format(str(vpn_id)))
# pylint: disable=no-member if isinstance(target, Service):
endpoint_ids = target.service_endpoint_ids if isinstance(target, Service) else target.slice_endpoint_ids endpoint_ids = target.service_endpoint_ids # pylint: disable=no-member
config_rules = target.service_config.config_rules # pylint: disable=no-member
for endpoint_id in endpoint_ids: constraints = target.service_constraints # pylint: disable=no-member
if endpoint_id.device_id.device_uuid.uuid != device_uuid: continue elif isinstance(target, Slice):
if endpoint_id.endpoint_uuid.uuid != endpoint_uuid: continue endpoint_ids = target.slice_endpoint_ids # pylint: disable=no-member
break # found, do nothing config_rules = target.slice_config.config_rules # pylint: disable=no-member
constraints = target.slice_constraints # pylint: disable=no-member
else: else:
# not found, add it raise Exception('Target({:s}) not supported'.format(str(target.__class__.__name__)))
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
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)
if 'mtu' not in json_settings: # missing, add it endpoint_id = update_endpoint_ids(endpoint_ids, device_uuid, endpoint_uuid)
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 service_settings_key = '/settings'
json_settings['address_families'] = DEFAULT_ADDRESS_FAMILIES update_config_rule_custom(config_rules, service_settings_key, {
elif json_settings['address_families'] != DEFAULT_ADDRESS_FAMILIES: # differs, raise exception 'mtu' : (DEFAULT_MTU, True),
msg = 'Specified AddressFamilies({:s}) differs from Service AddressFamilies({:s})' 'address_families': (DEFAULT_ADDRESS_FAMILIES, True),
raise Exception(msg.format(str(json_settings['address_families']), str(DEFAULT_ADDRESS_FAMILIES))) 'bgp_as' : (DEFAULT_BGP_AS, True),
'bgp_route_target': (DEFAULT_BGP_ROUTE_TARGET, True),
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)
endpoint_settings_key = '/device[{:s}]/endpoint[{:s}]/settings'.format(device_uuid, endpoint_uuid) 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 field_updates = {
if config_rule.WhichOneof('config_rule') != 'custom': continue 'router_id' : (router_id, True),
if config_rule.custom.resource_key != endpoint_settings_key: continue 'route_distinguisher': (route_distinguisher, True),
json_settings = json.loads(config_rule.custom.resource_value) 'sub_interface_index': (sub_if_index, True),
'vlan_id' : (cvlan_id, True),
if 'router_id' not in json_settings: # missing, add it }
json_settings['router_id'] = router_id if address_ip is not None: field_updates['address_ip' ] = (address_ip, True)
elif json_settings['router_id'] != router_id: # differs, raise exception if address_prefix is not None: field_updates['address_prefix' ] = (address_prefix, True)
msg = 'Specified RouterId({:s}) differs from Service RouterId({:s})' update_config_rule_custom(config_rules, endpoint_settings_key, field_updates)
raise Exception(msg.format(str(json_settings['router_id']), str(router_id)))
field_updates = {}
if 'route_distinguisher' not in json_settings: # missing, add it if len(diversity_constraints) > 0:
json_settings['route_distinguisher'] = route_distinguisher field_updates.update(diversity_constraints)
elif json_settings['route_distinguisher'] != route_distinguisher: # differs, raise exception update_constraint_custom(constraints, 'diversity', field_updates)
msg = 'Specified RouteDistinguisher({:s}) differs from Service RouteDistinguisher({:s})'
raise Exception(msg.format(str(json_settings['route_distinguisher']), str(route_distinguisher))) 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 'sub_interface_index' not in json_settings: # missing, add it if single_active or all_active:
json_settings['sub_interface_index'] = sub_if_index # assume 1 disjoint path per endpoint/location included in service/slice
elif json_settings['sub_interface_index'] != sub_if_index: # differs, raise exception location_endpoints = {}
msg = 'Specified SubInterfaceIndex({:s}) differs from Service SubInterfaceIndex({:s})' for constraint in constraints:
raise Exception(msg.format( if constraint.WhichOneof('constraint') != 'endpoint_location': continue
str(json_settings['sub_interface_index']), str(sub_if_index))) 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)
if 'vlan_id' not in json_settings: # missing, add it location_endpoints.setdefault(str_location_id, set()).add(str_endpoint_id)
json_settings['vlan_id'] = cvlan_id num_endpoints_per_location = {len(endpoints) for endpoints in location_endpoints.values()}
elif json_settings['vlan_id'] != cvlan_id: # differs, raise exception num_disjoint_paths = min(num_endpoints_per_location)
msg = 'Specified VLANId({:s}) differs from Service VLANId({:s})' update_constraint_sla_availability(constraints, num_disjoint_paths, all_active)
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,
}
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)
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
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)
return target return target
def process_list_site_network_access( 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 request_data : Dict
) -> Response: ) -> Response:
...@@ -216,7 +135,7 @@ def process_list_site_network_access( ...@@ -216,7 +135,7 @@ def process_list_site_network_access(
errors = [] errors = []
for site_network_access in request_data['ietf-l2vpn-svc:site-network-access']: 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))) LOGGER.debug('sna_request = {:s}'.format(grpc_message_to_json_string(sna_request)))
try: try:
if isinstance(sna_request, Service): if isinstance(sna_request, Service):
...@@ -230,7 +149,7 @@ def process_list_site_network_access( ...@@ -230,7 +149,7 @@ def process_list_site_network_access(
else: else:
raise NotImplementedError('Support for Class({:s}) not implemented'.format(str(type(sna_request)))) raise NotImplementedError('Support for Class({:s}) not implemented'.format(str(type(sna_request))))
except Exception as e: # pylint: disable=broad-except 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))) LOGGER.exception(msg.format(grpc_message_to_json_string(sna_request)))
errors.append({'error': str(e)}) errors.append({'error': str(e)})
time.sleep(random.random() / 10.0) time.sleep(random.random() / 10.0)
...@@ -247,7 +166,7 @@ class L2VPN_SiteNetworkAccesses(Resource): ...@@ -247,7 +166,7 @@ class L2VPN_SiteNetworkAccesses(Resource):
context_client = ContextClient() context_client = ContextClient()
service_client = ServiceClient() service_client = ServiceClient()
slice_client = SliceClient() 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 @HTTP_AUTH.login_required
def put(self, site_id : str): def put(self, site_id : str):
...@@ -256,4 +175,4 @@ class L2VPN_SiteNetworkAccesses(Resource): ...@@ -256,4 +175,4 @@ class L2VPN_SiteNetworkAccesses(Resource):
context_client = ContextClient() context_client = ContextClient()
service_client = ServiceClient() service_client = ServiceClient()
slice_client = SliceClient() 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)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment