Loading src/nbi/service/ietf_l2vpn/Handlers.py +130 −169 Original line number Diff line number Diff line Loading @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging, netaddr from typing import Dict, List, Optional, Tuple import logging from typing import Dict, List, Optional from common.Constants import DEFAULT_CONTEXT_NAME from common.proto.context_pb2 import Service, ServiceStatusEnum, ServiceTypeEnum from common.tools.context_queries.Service import get_service_by_uuid Loading @@ -27,9 +27,10 @@ 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 from nbi.service._tools.Authentication import HTTP_AUTH from nbi.service._tools.HttpStatusCodes import HTTP_NOCONTENT, HTTP_SERVERERROR from .Constants import BEARER_MAPPINGS, DEFAULT_ADDRESS_FAMILIES, DEFAULT_BGP_AS, DEFAULT_BGP_ROUTE_TARGET, DEFAULT_MTU from .Constants import ( #DEFAULT_ADDRESS_FAMILIES, DEFAULT_BGP_AS, DEFAULT_BGP_ROUTE_TARGET, BEARER_MAPPINGS, DEFAULT_MTU, ) LOGGER = logging.getLogger(__name__) Loading Loading @@ -62,8 +63,7 @@ def process_vpn_service( def process_site_network_access( site_id : str, network_access : Dict, errors : List[Dict] ) -> None: endpoint_uuid = network_access['site-network-access-id'] try: site_network_access_type = network_access['site-network-access-type'] site_network_access_type = site_network_access_type.replace('ietf-l2vpn-svc:', '') if site_network_access_type != 'multipoint': Loading @@ -71,20 +71,21 @@ def process_site_network_access( msg = MSG.format(str(network_access['site-network-access-type'])) raise NotImplementedError(msg) device_uuid = network_access['device-reference'] service_uuid = network_access['vpn-attachment']['vpn-id'] access_role : str = network_access['vpn-attachment']['site-role'] access_role = access_role.replace('ietf-l2vpn-svc:', '').replace('-role', '') # hub/spoke if access_role not in {'hub', 'spoke'}: MSG = 'Site VPN Attackment Role: {:s}' raise NotImplementedError(MSG.format(str(network_access['site-network-access-type']))) device_uuid = network_access['device-reference'] endpoint_uuid = network_access['site-network-access-id'] service_uuid = network_access['vpn-attachment']['vpn-id'] encapsulation_type = network_access['connection']['encapsulation-type'] cvlan_tag_id = network_access['connection']['tagged-interface'][encapsulation_type]['cvlan-id'] bearer_reference = network_access['bearer']['bearer-reference'] service_mtu = network_access['service']['svc-mtu'] service_input_bandwidth = network_access['service']['svc-input-bandwidth'] service_output_bandwidth = network_access['service']['svc-output-bandwidth'] Loading @@ -98,70 +99,17 @@ def process_site_network_access( MSG = 'Site Network Access QoS Class Id: {:s}' raise NotImplementedError(MSG.format(str(qos_profile_class['class-id']))) if 'ietf-l2vpn-svc' in qos_profile_class['direction']: # replace 'ietf-l2vpn-svc:both' with 'both' for backward compatibility qos_profile_class['direction'] = qos_profile_class['direction'].replace('ietf-l2vpn-svc:', '') if qos_profile_class['direction'] != 'both': qos_profile_class_direction = qos_profile_class['direction'] qos_profile_class_direction = qos_profile_class_direction.replace('ietf-l2vpn-svc:', '') if qos_profile_class_direction != 'both': MSG = 'Site Network Access QoS Class Direction: {:s}' raise NotImplementedError(MSG.format(str(qos_profile_class['direction']))) max_e2e_latency_ms = qos_profile_class['latency']['latency-boundary'] availability = qos_profile_class['bandwidth']['guaranteed-bw-percent'] errors.append({'error': str(exc)}) context_uuid : Optional[str] = DEFAULT_CONTEXT_NAME context_client = ContextClient() service = get_service_by_uuid(context_client, service_uuid, context_uuid=context_uuid, rw_copy=True) if service is None: raise Exception('VPN({:s}) not found in database'.format(str(service_uuid))) endpoint_ids = service.service_endpoint_ids endpoint_id = update_endpoint_ids(endpoint_ids, device_uuid, endpoint_uuid) constraints = service.service_constraints update_constraint_endpoint_location(constraints, endpoint_id, region=site_id) if service_bandwidth_gbps is not None: update_constraint_sla_capacity (constraints, service_bandwidth_gbps) if max_e2e_latency_ms is not None: update_constraint_sla_latency (constraints, max_e2e_latency_ms) if availability is not None: update_constraint_sla_availability(constraints, 1, True, availability) config_rules = service.service_config.config_rules service_settings_key = '/settings' service_settings = dict() if service_mtu is not None: service_settings['mtu'] = (service_mtu, True) update_config_rule_custom(config_rules, service_settings_key, service_settings) #ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/vlan[{:d}]/settings' #endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid, cvlan_tag_id) ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/settings' endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid) field_updates = {} if cvlan_tag_id is not None: field_updates['vlan_tag'] = (cvlan_tag_id, True) update_config_rule_custom(config_rules, endpoint_settings_key, field_updates) try: service_client = ServiceClient() service_client.UpdateService(service) return None except Exception as e: # pylint: disable=broad-except LOGGER.exception('Unhandled exception updating Service') return e # TODO: merge with def process_site_network_access(site_id : str, network_access : Dict) -> Service: service_uuid = network_access['vpn-attachment']['vpn-id'] bearer_reference = network_access['bearer']['bearer-reference'] access_priority : Optional[int] = network_access.get('availability', {}).get('access-priority') single_active : bool = len(network_access.get('availability', {}).get('single-active', [])) > 0 all_active : bool = len(network_access.get('availability', {}).get('all-active', [])) > 0 diversity_constraints = network_access.get('access-diversity', {}).get('constraints', {}).get('constraint', []) network_access_diversity = network_access.get('access-diversity', {}) diversity_constraints = network_access_diversity.get('constraints', {}).get('constraint', []) raise_if_differs = True diversity_constraints = { constraint['constraint-type']:([ Loading @@ -172,6 +120,11 @@ def process_site_network_access(site_id : str, network_access : Dict) -> Service for constraint in diversity_constraints } network_access_availability = network_access.get('availability', {}) access_priority : Optional[int] = network_access_availability.get('access-priority') single_active : bool = len(network_access_availability.get('single-active', [])) > 0 all_active : bool = len(network_access_availability.get('all-active', [])) > 0 mapping = BEARER_MAPPINGS.get(bearer_reference) if mapping is None: msg = 'Specified Bearer({:s}) is not configured.' Loading @@ -181,42 +134,32 @@ def process_site_network_access(site_id : str, network_access : Dict) -> Service address_ip, address_prefix, remote_router, circuit_id ) = mapping target = get_service_by_uuid(context_client, service_uuid, rw_copy=True) if target is None: raise Exception('VPN({:s}) not found in database'.format(str(service_uuid))) context_client = ContextClient() service = get_service_by_uuid( context_client, service_uuid, context_uuid=DEFAULT_CONTEXT_NAME, rw_copy=True ) if service is None: raise Exception('VPN({:s}) not found in database'.format(str(service_uuid))) 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 endpoint_ids = service.service_endpoint_ids config_rules = service.service_config.config_rules constraints = service.service_constraints endpoint_id = update_endpoint_ids(endpoint_ids, device_uuid, endpoint_uuid) 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) field_updates = {} if router_id is not None: field_updates['router_id' ] = (router_id, True) if route_dist is not None: field_updates['route_distinguisher'] = (route_dist, True) if sub_if_index is not None: field_updates['sub_interface_index'] = (sub_if_index, True) if cvlan_id is not None: field_updates['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) if remote_router is not None: field_updates['remote_router' ] = (remote_router, True) if circuit_id is not None: field_updates['circuit_id' ] = (circuit_id, True) update_config_rule_custom(config_rules, endpoint_settings_key, 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 service_bandwidth_gbps is not None: update_constraint_sla_capacity(constraints, service_bandwidth_gbps) if max_e2e_latency_ms is not None: update_constraint_sla_latency(constraints, max_e2e_latency_ms) if availability is not None: update_constraint_sla_availability(constraints, 1, True, availability) if len(diversity_constraints) > 0: update_constraint_custom_dict(constraints, 'diversity', diversity_constraints) 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 # assume 1 disjoint path per endpoint/location included in service location_endpoints = {} for constraint in constraints: if constraint.WhichOneof('constraint') != 'endpoint_location': continue Loading @@ -227,17 +170,35 @@ def process_site_network_access(site_id : str, network_access : Dict) -> Service num_disjoint_paths = max(num_endpoints_per_location) update_constraint_sla_availability(constraints, num_disjoint_paths, all_active, 0.0) return target service_settings_key = '/settings' if service_mtu is None: service_mtu = DEFAULT_MTU update_config_rule_custom(config_rules, service_settings_key, { 'mtu' : (service_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}]/vlan[{:d}]/settings' #endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid, cvlan_tag_id) ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/settings' endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid) field_updates = {} if router_id is not None: field_updates['router_id' ] = (router_id, True) if route_dist is not None: field_updates['route_distinguisher'] = (route_dist, True) if sub_if_index is not None: field_updates['sub_interface_index'] = (sub_if_index, True) if cvlan_tag_id is not None: field_updates['vlan_id' ] = (cvlan_tag_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) if remote_router is not None: field_updates['remote_router' ] = (remote_router, True) if circuit_id is not None: field_updates['circuit_id' ] = (circuit_id, True) update_config_rule_custom(config_rules, endpoint_settings_key, field_updates) service_client = ServiceClient() service_client.UpdateService(service) except Exception as exc: LOGGER.exception('Unhandled Exception') errors.append({'error': str(exc)}) def process_site(site : Dict, errors : List[Dict]) -> None: Loading src/nbi/service/ietf_l2vpn/L2VPN_SiteNetworkAccesses.py +138 −25 Original line number Diff line number Diff line Loading @@ -13,44 +13,157 @@ # limitations under the License. import logging from typing import Dict from typing import Dict, List 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 ServiceTypeEnum from common.tools.context_queries.Service import get_services from context.client.ContextClient import ContextClient from nbi.service._tools.Authentication import HTTP_AUTH from nbi.service._tools.HttpStatusCodes import HTTP_CREATED, HTTP_SERVERERROR from nbi.service._tools.HttpStatusCodes import ( HTTP_CREATED, HTTP_NOCONTENT, HTTP_SERVERERROR ) from .Handlers import process_site_network_access from .YangValidator import YangValidator LOGGER = logging.getLogger(__name__) def process_site_network_accesses(site_id : str) -> Response: class L2VPN_SiteNetworkAccesses(Resource): @HTTP_AUTH.login_required def post(self, site_id : str): if not request.is_json: raise UnsupportedMediaType('JSON payload is required') request_data : Dict = request.json LOGGER.debug('Site_Id: {:s}'.format(str(site_id))) LOGGER.debug('Request: {:s}'.format(str(request_data))) yang_validator = YangValidator('ietf-l2vpn-svc') request_data = yang_validator.parse_to_dict(request_data) yang_validator.destroy() errors = [] for network_access in request_data['site-network-accesses']['site-network-access']: exc = process_site_network_access(site_id, network_access) if exc is not None: errors.append({'error': str(exc)}) errors = self._process_site_network_accesses(site_id, request_data) response = jsonify(errors) response.status_code = HTTP_CREATED if len(errors) == 0 else HTTP_SERVERERROR return response class L2VPN_SiteNetworkAccesses(Resource): @HTTP_AUTH.login_required def post(self, site_id : str): return process_site_network_accesses(site_id) @HTTP_AUTH.login_required def put(self, site_id : str): return process_site_network_accesses(site_id) if not request.is_json: raise UnsupportedMediaType('JSON payload is required') request_data : Dict = request.json LOGGER.debug('Site_Id: {:s}'.format(str(site_id))) LOGGER.debug('Request: {:s}'.format(str(request_data))) errors = self._process_site_network_accesses(site_id, request_data) response = jsonify(errors) response.status_code = HTTP_NOCONTENT if len(errors) == 0 else HTTP_SERVERERROR return response def _prepare_request_payload(self, site_id : str, request_data : Dict, errors : List[Dict]) -> Dict: if 'ietf-l2vpn-svc:l2vpn-svc' in request_data: # processing single (standard) request formatted as: #{"ietf-l2vpn-svc:l2vpn-svc": { # "sites": {"site": [ # { # "site-id": ..., # "site-network-accesses": {"site-network-access": [ # { # "network-access-id": ..., # ... # } # ]} # } # ]} #}} return request_data if 'ietf-l2vpn-svc:site-network-access' in request_data: # processing OSM-style payload request formatted as: #{ # "ietf-l2vpn-svc:site-network-access": [ site_network_accesses = request_data['ietf-l2vpn-svc:site-network-access'] location_refs = set() location_refs.add('fake-location') # Add mandatory fields OSM RO driver skips and fix wrong ones for site_network_access in site_network_accesses: if 'location-reference' in site_network_access: location_refs.add(site_network_access['location-reference']) else: site_network_access['location-reference'] = 'fake-location' if 'connection' in site_network_access: connection = site_network_access['connection'] if 'encapsulation-type' in connection: if connection['encapsulation-type'] == 'dot1q-vlan-tagged': connection['encapsulation-type'] = 'vlan' else: connection['encapsulation-type'] = 'ethernet' if 'tagged-interface' in connection: tagged_interface = connection['tagged-interface'] if 'dot1q-vlan-tagged' in tagged_interface: if 'type' not in tagged_interface: tagged_interface['type'] = 'dot1q' if 'oam' not in connection: connection['oam'] = dict() if 'md-name' not in connection['oam']: connection['oam']['md-name'] = 'fake-md-name' if 'md-level' not in connection['oam']: connection['oam']['md-level'] = 0 if 'service' not in site_network_access: site_network_access['service'] = dict() if 'svc-mtu' not in site_network_access['service']: site_network_access['service']['svc-mtu'] = 1500 context_client = ContextClient() vpn_services = list() for service in get_services(context_client): if service.service_type != ServiceTypeEnum.SERVICETYPE_L2NM: continue vpn_ids = [service.service_id.service_uuid.uuid, service.name] for vpn_id in vpn_ids: vpn_services.append({ 'vpn-id': vpn_id, 'frame-delivery': { 'multicast-gp-port-mapping': 'ietf-l2vpn-svc:static-mapping' }, 'ce-vlan-preservation': True, 'ce-vlan-cos-preservation': True, }) request_data = {'ietf-l2vpn-svc:l2vpn-svc': { 'vpn-services': { 'vpn-service': vpn_services }, 'sites': {'site': [{ 'site-id': site_id, 'default-ce-vlan-id': 1, 'management': {'type': 'customer-managed'}, 'locations': {'location': [ {'location-id': location_ref} for location_ref in location_refs ]}, 'site-network-accesses': { 'site-network-access': site_network_accesses } }]} }} return request_data errors.append('Unexpected request: {:s}'.format(str(request_data))) return None def _process_site_network_accesses(self, site_id : str, request_data : Dict) -> List[Dict]: errors = list() request_data = self._prepare_request_payload(site_id, request_data, errors) if len(errors) > 0: return errors yang_validator = YangValidator('ietf-l2vpn-svc') request_data = yang_validator.parse_to_dict(request_data) yang_validator.destroy() site_network_accesses = ( request_data.get('site-network-accesses', dict()) .get('site-network-access', list()) ) for site_network_access in site_network_accesses: process_site_network_access(site_id, site_network_access, errors) return errors Loading
src/nbi/service/ietf_l2vpn/Handlers.py +130 −169 Original line number Diff line number Diff line Loading @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging, netaddr from typing import Dict, List, Optional, Tuple import logging from typing import Dict, List, Optional from common.Constants import DEFAULT_CONTEXT_NAME from common.proto.context_pb2 import Service, ServiceStatusEnum, ServiceTypeEnum from common.tools.context_queries.Service import get_service_by_uuid Loading @@ -27,9 +27,10 @@ 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 from nbi.service._tools.Authentication import HTTP_AUTH from nbi.service._tools.HttpStatusCodes import HTTP_NOCONTENT, HTTP_SERVERERROR from .Constants import BEARER_MAPPINGS, DEFAULT_ADDRESS_FAMILIES, DEFAULT_BGP_AS, DEFAULT_BGP_ROUTE_TARGET, DEFAULT_MTU from .Constants import ( #DEFAULT_ADDRESS_FAMILIES, DEFAULT_BGP_AS, DEFAULT_BGP_ROUTE_TARGET, BEARER_MAPPINGS, DEFAULT_MTU, ) LOGGER = logging.getLogger(__name__) Loading Loading @@ -62,8 +63,7 @@ def process_vpn_service( def process_site_network_access( site_id : str, network_access : Dict, errors : List[Dict] ) -> None: endpoint_uuid = network_access['site-network-access-id'] try: site_network_access_type = network_access['site-network-access-type'] site_network_access_type = site_network_access_type.replace('ietf-l2vpn-svc:', '') if site_network_access_type != 'multipoint': Loading @@ -71,20 +71,21 @@ def process_site_network_access( msg = MSG.format(str(network_access['site-network-access-type'])) raise NotImplementedError(msg) device_uuid = network_access['device-reference'] service_uuid = network_access['vpn-attachment']['vpn-id'] access_role : str = network_access['vpn-attachment']['site-role'] access_role = access_role.replace('ietf-l2vpn-svc:', '').replace('-role', '') # hub/spoke if access_role not in {'hub', 'spoke'}: MSG = 'Site VPN Attackment Role: {:s}' raise NotImplementedError(MSG.format(str(network_access['site-network-access-type']))) device_uuid = network_access['device-reference'] endpoint_uuid = network_access['site-network-access-id'] service_uuid = network_access['vpn-attachment']['vpn-id'] encapsulation_type = network_access['connection']['encapsulation-type'] cvlan_tag_id = network_access['connection']['tagged-interface'][encapsulation_type]['cvlan-id'] bearer_reference = network_access['bearer']['bearer-reference'] service_mtu = network_access['service']['svc-mtu'] service_input_bandwidth = network_access['service']['svc-input-bandwidth'] service_output_bandwidth = network_access['service']['svc-output-bandwidth'] Loading @@ -98,70 +99,17 @@ def process_site_network_access( MSG = 'Site Network Access QoS Class Id: {:s}' raise NotImplementedError(MSG.format(str(qos_profile_class['class-id']))) if 'ietf-l2vpn-svc' in qos_profile_class['direction']: # replace 'ietf-l2vpn-svc:both' with 'both' for backward compatibility qos_profile_class['direction'] = qos_profile_class['direction'].replace('ietf-l2vpn-svc:', '') if qos_profile_class['direction'] != 'both': qos_profile_class_direction = qos_profile_class['direction'] qos_profile_class_direction = qos_profile_class_direction.replace('ietf-l2vpn-svc:', '') if qos_profile_class_direction != 'both': MSG = 'Site Network Access QoS Class Direction: {:s}' raise NotImplementedError(MSG.format(str(qos_profile_class['direction']))) max_e2e_latency_ms = qos_profile_class['latency']['latency-boundary'] availability = qos_profile_class['bandwidth']['guaranteed-bw-percent'] errors.append({'error': str(exc)}) context_uuid : Optional[str] = DEFAULT_CONTEXT_NAME context_client = ContextClient() service = get_service_by_uuid(context_client, service_uuid, context_uuid=context_uuid, rw_copy=True) if service is None: raise Exception('VPN({:s}) not found in database'.format(str(service_uuid))) endpoint_ids = service.service_endpoint_ids endpoint_id = update_endpoint_ids(endpoint_ids, device_uuid, endpoint_uuid) constraints = service.service_constraints update_constraint_endpoint_location(constraints, endpoint_id, region=site_id) if service_bandwidth_gbps is not None: update_constraint_sla_capacity (constraints, service_bandwidth_gbps) if max_e2e_latency_ms is not None: update_constraint_sla_latency (constraints, max_e2e_latency_ms) if availability is not None: update_constraint_sla_availability(constraints, 1, True, availability) config_rules = service.service_config.config_rules service_settings_key = '/settings' service_settings = dict() if service_mtu is not None: service_settings['mtu'] = (service_mtu, True) update_config_rule_custom(config_rules, service_settings_key, service_settings) #ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/vlan[{:d}]/settings' #endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid, cvlan_tag_id) ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/settings' endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid) field_updates = {} if cvlan_tag_id is not None: field_updates['vlan_tag'] = (cvlan_tag_id, True) update_config_rule_custom(config_rules, endpoint_settings_key, field_updates) try: service_client = ServiceClient() service_client.UpdateService(service) return None except Exception as e: # pylint: disable=broad-except LOGGER.exception('Unhandled exception updating Service') return e # TODO: merge with def process_site_network_access(site_id : str, network_access : Dict) -> Service: service_uuid = network_access['vpn-attachment']['vpn-id'] bearer_reference = network_access['bearer']['bearer-reference'] access_priority : Optional[int] = network_access.get('availability', {}).get('access-priority') single_active : bool = len(network_access.get('availability', {}).get('single-active', [])) > 0 all_active : bool = len(network_access.get('availability', {}).get('all-active', [])) > 0 diversity_constraints = network_access.get('access-diversity', {}).get('constraints', {}).get('constraint', []) network_access_diversity = network_access.get('access-diversity', {}) diversity_constraints = network_access_diversity.get('constraints', {}).get('constraint', []) raise_if_differs = True diversity_constraints = { constraint['constraint-type']:([ Loading @@ -172,6 +120,11 @@ def process_site_network_access(site_id : str, network_access : Dict) -> Service for constraint in diversity_constraints } network_access_availability = network_access.get('availability', {}) access_priority : Optional[int] = network_access_availability.get('access-priority') single_active : bool = len(network_access_availability.get('single-active', [])) > 0 all_active : bool = len(network_access_availability.get('all-active', [])) > 0 mapping = BEARER_MAPPINGS.get(bearer_reference) if mapping is None: msg = 'Specified Bearer({:s}) is not configured.' Loading @@ -181,42 +134,32 @@ def process_site_network_access(site_id : str, network_access : Dict) -> Service address_ip, address_prefix, remote_router, circuit_id ) = mapping target = get_service_by_uuid(context_client, service_uuid, rw_copy=True) if target is None: raise Exception('VPN({:s}) not found in database'.format(str(service_uuid))) context_client = ContextClient() service = get_service_by_uuid( context_client, service_uuid, context_uuid=DEFAULT_CONTEXT_NAME, rw_copy=True ) if service is None: raise Exception('VPN({:s}) not found in database'.format(str(service_uuid))) 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 endpoint_ids = service.service_endpoint_ids config_rules = service.service_config.config_rules constraints = service.service_constraints endpoint_id = update_endpoint_ids(endpoint_ids, device_uuid, endpoint_uuid) 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) field_updates = {} if router_id is not None: field_updates['router_id' ] = (router_id, True) if route_dist is not None: field_updates['route_distinguisher'] = (route_dist, True) if sub_if_index is not None: field_updates['sub_interface_index'] = (sub_if_index, True) if cvlan_id is not None: field_updates['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) if remote_router is not None: field_updates['remote_router' ] = (remote_router, True) if circuit_id is not None: field_updates['circuit_id' ] = (circuit_id, True) update_config_rule_custom(config_rules, endpoint_settings_key, 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 service_bandwidth_gbps is not None: update_constraint_sla_capacity(constraints, service_bandwidth_gbps) if max_e2e_latency_ms is not None: update_constraint_sla_latency(constraints, max_e2e_latency_ms) if availability is not None: update_constraint_sla_availability(constraints, 1, True, availability) if len(diversity_constraints) > 0: update_constraint_custom_dict(constraints, 'diversity', diversity_constraints) 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 # assume 1 disjoint path per endpoint/location included in service location_endpoints = {} for constraint in constraints: if constraint.WhichOneof('constraint') != 'endpoint_location': continue Loading @@ -227,17 +170,35 @@ def process_site_network_access(site_id : str, network_access : Dict) -> Service num_disjoint_paths = max(num_endpoints_per_location) update_constraint_sla_availability(constraints, num_disjoint_paths, all_active, 0.0) return target service_settings_key = '/settings' if service_mtu is None: service_mtu = DEFAULT_MTU update_config_rule_custom(config_rules, service_settings_key, { 'mtu' : (service_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}]/vlan[{:d}]/settings' #endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid, cvlan_tag_id) ENDPOINT_SETTINGS_KEY = '/device[{:s}]/endpoint[{:s}]/settings' endpoint_settings_key = ENDPOINT_SETTINGS_KEY.format(device_uuid, endpoint_uuid) field_updates = {} if router_id is not None: field_updates['router_id' ] = (router_id, True) if route_dist is not None: field_updates['route_distinguisher'] = (route_dist, True) if sub_if_index is not None: field_updates['sub_interface_index'] = (sub_if_index, True) if cvlan_tag_id is not None: field_updates['vlan_id' ] = (cvlan_tag_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) if remote_router is not None: field_updates['remote_router' ] = (remote_router, True) if circuit_id is not None: field_updates['circuit_id' ] = (circuit_id, True) update_config_rule_custom(config_rules, endpoint_settings_key, field_updates) service_client = ServiceClient() service_client.UpdateService(service) except Exception as exc: LOGGER.exception('Unhandled Exception') errors.append({'error': str(exc)}) def process_site(site : Dict, errors : List[Dict]) -> None: Loading
src/nbi/service/ietf_l2vpn/L2VPN_SiteNetworkAccesses.py +138 −25 Original line number Diff line number Diff line Loading @@ -13,44 +13,157 @@ # limitations under the License. import logging from typing import Dict from typing import Dict, List 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 ServiceTypeEnum from common.tools.context_queries.Service import get_services from context.client.ContextClient import ContextClient from nbi.service._tools.Authentication import HTTP_AUTH from nbi.service._tools.HttpStatusCodes import HTTP_CREATED, HTTP_SERVERERROR from nbi.service._tools.HttpStatusCodes import ( HTTP_CREATED, HTTP_NOCONTENT, HTTP_SERVERERROR ) from .Handlers import process_site_network_access from .YangValidator import YangValidator LOGGER = logging.getLogger(__name__) def process_site_network_accesses(site_id : str) -> Response: class L2VPN_SiteNetworkAccesses(Resource): @HTTP_AUTH.login_required def post(self, site_id : str): if not request.is_json: raise UnsupportedMediaType('JSON payload is required') request_data : Dict = request.json LOGGER.debug('Site_Id: {:s}'.format(str(site_id))) LOGGER.debug('Request: {:s}'.format(str(request_data))) yang_validator = YangValidator('ietf-l2vpn-svc') request_data = yang_validator.parse_to_dict(request_data) yang_validator.destroy() errors = [] for network_access in request_data['site-network-accesses']['site-network-access']: exc = process_site_network_access(site_id, network_access) if exc is not None: errors.append({'error': str(exc)}) errors = self._process_site_network_accesses(site_id, request_data) response = jsonify(errors) response.status_code = HTTP_CREATED if len(errors) == 0 else HTTP_SERVERERROR return response class L2VPN_SiteNetworkAccesses(Resource): @HTTP_AUTH.login_required def post(self, site_id : str): return process_site_network_accesses(site_id) @HTTP_AUTH.login_required def put(self, site_id : str): return process_site_network_accesses(site_id) if not request.is_json: raise UnsupportedMediaType('JSON payload is required') request_data : Dict = request.json LOGGER.debug('Site_Id: {:s}'.format(str(site_id))) LOGGER.debug('Request: {:s}'.format(str(request_data))) errors = self._process_site_network_accesses(site_id, request_data) response = jsonify(errors) response.status_code = HTTP_NOCONTENT if len(errors) == 0 else HTTP_SERVERERROR return response def _prepare_request_payload(self, site_id : str, request_data : Dict, errors : List[Dict]) -> Dict: if 'ietf-l2vpn-svc:l2vpn-svc' in request_data: # processing single (standard) request formatted as: #{"ietf-l2vpn-svc:l2vpn-svc": { # "sites": {"site": [ # { # "site-id": ..., # "site-network-accesses": {"site-network-access": [ # { # "network-access-id": ..., # ... # } # ]} # } # ]} #}} return request_data if 'ietf-l2vpn-svc:site-network-access' in request_data: # processing OSM-style payload request formatted as: #{ # "ietf-l2vpn-svc:site-network-access": [ site_network_accesses = request_data['ietf-l2vpn-svc:site-network-access'] location_refs = set() location_refs.add('fake-location') # Add mandatory fields OSM RO driver skips and fix wrong ones for site_network_access in site_network_accesses: if 'location-reference' in site_network_access: location_refs.add(site_network_access['location-reference']) else: site_network_access['location-reference'] = 'fake-location' if 'connection' in site_network_access: connection = site_network_access['connection'] if 'encapsulation-type' in connection: if connection['encapsulation-type'] == 'dot1q-vlan-tagged': connection['encapsulation-type'] = 'vlan' else: connection['encapsulation-type'] = 'ethernet' if 'tagged-interface' in connection: tagged_interface = connection['tagged-interface'] if 'dot1q-vlan-tagged' in tagged_interface: if 'type' not in tagged_interface: tagged_interface['type'] = 'dot1q' if 'oam' not in connection: connection['oam'] = dict() if 'md-name' not in connection['oam']: connection['oam']['md-name'] = 'fake-md-name' if 'md-level' not in connection['oam']: connection['oam']['md-level'] = 0 if 'service' not in site_network_access: site_network_access['service'] = dict() if 'svc-mtu' not in site_network_access['service']: site_network_access['service']['svc-mtu'] = 1500 context_client = ContextClient() vpn_services = list() for service in get_services(context_client): if service.service_type != ServiceTypeEnum.SERVICETYPE_L2NM: continue vpn_ids = [service.service_id.service_uuid.uuid, service.name] for vpn_id in vpn_ids: vpn_services.append({ 'vpn-id': vpn_id, 'frame-delivery': { 'multicast-gp-port-mapping': 'ietf-l2vpn-svc:static-mapping' }, 'ce-vlan-preservation': True, 'ce-vlan-cos-preservation': True, }) request_data = {'ietf-l2vpn-svc:l2vpn-svc': { 'vpn-services': { 'vpn-service': vpn_services }, 'sites': {'site': [{ 'site-id': site_id, 'default-ce-vlan-id': 1, 'management': {'type': 'customer-managed'}, 'locations': {'location': [ {'location-id': location_ref} for location_ref in location_refs ]}, 'site-network-accesses': { 'site-network-access': site_network_accesses } }]} }} return request_data errors.append('Unexpected request: {:s}'.format(str(request_data))) return None def _process_site_network_accesses(self, site_id : str, request_data : Dict) -> List[Dict]: errors = list() request_data = self._prepare_request_payload(site_id, request_data, errors) if len(errors) > 0: return errors yang_validator = YangValidator('ietf-l2vpn-svc') request_data = yang_validator.parse_to_dict(request_data) yang_validator.destroy() site_network_accesses = ( request_data.get('site-network-accesses', dict()) .get('site-network-access', list()) ) for site_network_access in site_network_accesses: process_site_network_access(site_id, site_network_access, errors) return errors