From 6f72b50460adb705b0e1f4e970aae387a5d1e102 Mon Sep 17 00:00:00 2001 From: armingol Date: Wed, 28 May 2025 09:46:49 +0200 Subject: [PATCH 1/2] NBI IETF-L3VPN PUT --- src/nbi/service/ietf_l3vpn/Handlers.py | 64 +++++++++++++++++++-- src/nbi/service/ietf_l3vpn/L3VPN_Service.py | 44 +++++++++++++- 2 files changed, 101 insertions(+), 7 deletions(-) diff --git a/src/nbi/service/ietf_l3vpn/Handlers.py b/src/nbi/service/ietf_l3vpn/Handlers.py index 61736db53..6bc972239 100644 --- a/src/nbi/service/ietf_l3vpn/Handlers.py +++ b/src/nbi/service/ietf_l3vpn/Handlers.py @@ -1,4 +1,4 @@ -# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -59,7 +59,7 @@ def update_service_endpoint( capacity_gbps : Optional[float] = None, e2e_latency_ms : Optional[float] = None, availability : Optional[float] = None, mtu : Optional[int] = None, static_routing : Optional[Dict[Tuple[str, str], str]] = None, - context_uuid : Optional[str] = DEFAULT_CONTEXT_NAME, + context_uuid : Optional[str] = DEFAULT_CONTEXT_NAME, ) -> Optional[Exception]: context_client = ContextClient() service = get_service_by_uuid(context_client, service_uuid, context_uuid=context_uuid, rw_copy=True) @@ -121,7 +121,7 @@ def process_site_network_access( 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-l3vpn-svc:', '').replace('-role', '') # hub/spoke if access_role not in {'hub', 'spoke'}: @@ -201,7 +201,7 @@ def process_site(site : Dict, errors : List[Dict]) -> None: if rt_proto['type'] != 'ietf-l3vpn-svc:static': MSG = 'Site Routing Protocol Type: {:s}' raise NotImplementedError(MSG.format(str(rt_proto['type']))) - + rt_proto_static : Dict = rt_proto.get('static', dict()) rt_proto_static_clps : Dict = rt_proto_static.get('cascaded-lan-prefixes', dict()) rt_proto_static_clps_v4 = rt_proto_static_clps.get('ipv4-lan-prefixes', list()) @@ -215,3 +215,59 @@ def process_site(site : Dict, errors : List[Dict]) -> None: network_accesses : List[Dict] = site['site-network-accesses']['site-network-access'] for network_access in network_accesses: process_site_network_access(site_id, network_access, site_static_routing, errors) + +def update_vpn(site : Dict, errors : List[Dict]) -> None: + + if site['management']['type'] != 'ietf-l3vpn-svc:provider-managed': + MSG = 'Site Management Type: {:s}' + raise NotImplementedError(MSG.format(str(site['management']['type']))) + + network_accesses : List[Dict] = site['site-network-accesses']['site-network-access'] + for network_access in network_accesses: + update_site_network_access(network_access, errors) + +def update_site_network_access( + network_access : Dict, errors : List[Dict] +) -> None: + + if network_access['site-network-access-type'] != 'ietf-l3vpn-svc:multipoint': + MSG = 'Site Network Access Type: {:s}' + raise NotImplementedError(MSG.format(str(network_access['site-network-access-type']))) + + service_uuid = network_access['vpn-attachment']['vpn-id'] + + service_input_bandwidth = network_access['service']['svc-input-bandwidth'] + service_output_bandwidth = network_access['service']['svc-output-bandwidth'] + service_bandwidth_bps = max(service_input_bandwidth, service_output_bandwidth) + service_bandwidth_gbps = service_bandwidth_bps / 1.e9 + + max_e2e_latency_ms = None + availability = None + + exc = update_site_endpoint( + service_uuid, capacity_gbps=service_bandwidth_gbps, + e2e_latency_ms=max_e2e_latency_ms, availability=availability, + ) + if exc is not None: errors.append({'error': str(exc)}) + +def update_site_endpoint( + service_uuid : str, capacity_gbps : Optional[float] = None, + e2e_latency_ms : Optional[float] = None, availability : Optional[float] = None, + context_uuid : Optional[str] = DEFAULT_CONTEXT_NAME +) -> Optional[Exception]: + 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))) + + constraints = service.service_constraints + if capacity_gbps is not None: update_constraint_sla_capacity (constraints, capacity_gbps) + if e2e_latency_ms is not None: update_constraint_sla_latency (constraints, e2e_latency_ms) + if availability is not None: update_constraint_sla_availability(constraints, 1, True, availability) + + 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 diff --git a/src/nbi/service/ietf_l3vpn/L3VPN_Service.py b/src/nbi/service/ietf_l3vpn/L3VPN_Service.py index acede1526..d551a60e4 100644 --- a/src/nbi/service/ietf_l3vpn/L3VPN_Service.py +++ b/src/nbi/service/ietf_l3vpn/L3VPN_Service.py @@ -1,4 +1,4 @@ -# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,8 +20,12 @@ from common.proto.context_pb2 import ServiceStatusEnum from common.tools.context_queries.Service import get_service_by_uuid 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_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK, HTTP_SERVERERROR +from typing import Dict, List +from ..tools.Authentication import HTTP_AUTH +from ..tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK, HTTP_SERVERERROR, HTTP_CREATED +from .Handlers import update_vpn +from werkzeug.exceptions import UnsupportedMediaType +from .YangValidator import YangValidator LOGGER = logging.getLogger(__name__) @@ -76,3 +80,37 @@ class L3VPN_Service(Resource): response = jsonify({'error': str(e)}) response.status_code = HTTP_SERVERERROR return response + + def put(self, vpn_id : str): + #TODO: check vpn_id with request service_id in body + if not request.is_json: raise UnsupportedMediaType('JSON payload is required') + request_data: Dict = request.json + LOGGER.debug('PUT Request: {:s}'.format(str(request_data))) + + errors = list() + + if 'ietf-l3vpn-svc:l3vpn-services' in request_data: + for l3vpn_svc in request_data['ietf-l3vpn-svc:l3vpn-services']['l3vpn-svc']: + l3vpn_svc.pop('service-id', None) + l3vpn_svc_request_data = {'ietf-l3vpn-svc:l3vpn-svc': l3vpn_svc} + errors.extend(self._update_l3vpn(l3vpn_svc_request_data)) + elif 'ietf-l3vpn-svc:l3vpn-svc' in request_data: + errors.extend(self._update_l3vpn(request_data)) + else: + errors.append('Unexpected request format: {:s}'.format(str(request_data))) + + response = jsonify(errors) + response.status_code = HTTP_CREATED if len(errors) == 0 else HTTP_SERVERERROR + return response + + def _update_l3vpn(self, request_data: Dict) -> List[Dict]: + yang_validator = YangValidator('ietf-l3vpn-svc') + request_data = yang_validator.parse_to_dict(request_data) + yang_validator.destroy() + + errors = list() + + for site in request_data['l3vpn-svc']['sites']['site']: + update_vpn(site, errors) + + return errors -- GitLab From 1d0c2fe7ecae80d8ee71d1112abc832e1217d1e4 Mon Sep 17 00:00:00 2001 From: armingol Date: Wed, 28 May 2025 09:48:31 +0200 Subject: [PATCH 2/2] code cleanup --- src/nbi/service/ietf_l3vpn/Handlers.py | 2 +- src/nbi/service/ietf_l3vpn/L3VPN_Service.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nbi/service/ietf_l3vpn/Handlers.py b/src/nbi/service/ietf_l3vpn/Handlers.py index 6bc972239..268ec49af 100644 --- a/src/nbi/service/ietf_l3vpn/Handlers.py +++ b/src/nbi/service/ietf_l3vpn/Handlers.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/nbi/service/ietf_l3vpn/L3VPN_Service.py b/src/nbi/service/ietf_l3vpn/L3VPN_Service.py index d551a60e4..1473356e7 100644 --- a/src/nbi/service/ietf_l3vpn/L3VPN_Service.py +++ b/src/nbi/service/ietf_l3vpn/L3VPN_Service.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. -- GitLab