diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/L2VPN_Service.py b/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/L2VPN_Service.py index 23f61b492b63acd8176fa4aeafc475d71c63ead6..ed2f01af10a262a7f51f308563f1e823ba6f19ad 100644 --- a/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/L2VPN_Service.py +++ b/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/L2VPN_Service.py @@ -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: - service_client = ServiceClient() - service_client.DeleteService(service_id_request) - response = jsonify({}) - response.status_code = HTTP_NOCONTENT + 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(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 diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/L2VPN_SiteNetworkAccesses.py b/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/L2VPN_SiteNetworkAccesses.py index 2abeade30b8b5658bfd401820c71474ebfad85d5..4019099401316e943b8ae1ae1442deb6b1ec1722 100644 --- a/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/L2VPN_SiteNetworkAccesses.py +++ b/src/compute/service/rest_server/nbi_plugins/ietf_l2vpn/L2VPN_SiteNetworkAccesses.py @@ -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 - - 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) + raise Exception('Target({:s}) not supported'.format(str(target.__class__.__name__))) - 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))) + endpoint_id = update_endpoint_ids(endpoint_ids, device_uuid, endpoint_uuid) - 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, - } - 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) + 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 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) + + field_updates = {} + if len(diversity_constraints) > 0: + 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)