From efcf88fae917dc660871d5e0246c4ecb3e8e332b Mon Sep 17 00:00:00 2001 From: Armingol Date: Mon, 13 Nov 2023 14:16:13 +0100 Subject: [PATCH] First version --- proto/device.proto | 2 + src/compute/service/__main__.py | 2 + .../nbi_plugins/ietf_inventory/Constants.py | 91 +++++++++++++++++ .../ietf_inventory/DeviceClient.py | 97 +++++++++++++++++++ .../ietf_inventory/IETF_Inventories.py | 60 ++++++++++++ .../ietf_inventory/IETF_Inventory.py | 48 +++++++++ .../nbi_plugins/ietf_inventory/__init__.py | 33 +++++++ .../ietf_inventory/schemas/Common.py | 16 +++ .../ietf_inventory/schemas/__init__.py | 14 +++ .../schemas/device_inventory.py | 50 ++++++++++ src/device/client/DeviceClient.py | 15 +++ .../service/DeviceServiceServicerImpl.py | 32 +++++- src/device/service/Tools.py | 13 ++- 13 files changed, 470 insertions(+), 3 deletions(-) create mode 100644 src/compute/service/rest_server/nbi_plugins/ietf_inventory/Constants.py create mode 100644 src/compute/service/rest_server/nbi_plugins/ietf_inventory/DeviceClient.py create mode 100644 src/compute/service/rest_server/nbi_plugins/ietf_inventory/IETF_Inventories.py create mode 100644 src/compute/service/rest_server/nbi_plugins/ietf_inventory/IETF_Inventory.py create mode 100644 src/compute/service/rest_server/nbi_plugins/ietf_inventory/__init__.py create mode 100644 src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/Common.py create mode 100644 src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/__init__.py create mode 100644 src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/device_inventory.py diff --git a/proto/device.proto b/proto/device.proto index 30e60079d..bf21353e3 100644 --- a/proto/device.proto +++ b/proto/device.proto @@ -24,6 +24,8 @@ service DeviceService { rpc DeleteDevice (context.DeviceId ) returns (context.Empty ) {} rpc GetInitialConfig(context.DeviceId ) returns (context.DeviceConfig) {} rpc MonitorDeviceKpi(MonitoringSettings) returns (context.Empty ) {} + rpc DeviceInventory (context.DeviceId) returns (context.Empty ) {} + } message MonitoringSettings { diff --git a/src/compute/service/__main__.py b/src/compute/service/__main__.py index a9f224e15..4373b6792 100644 --- a/src/compute/service/__main__.py +++ b/src/compute/service/__main__.py @@ -24,6 +24,7 @@ from .rest_server.nbi_plugins.debug_api import register_debug_api from .rest_server.nbi_plugins.etsi_bwm import register_etsi_bwm_api from .rest_server.nbi_plugins.ietf_l2vpn import register_ietf_l2vpn from .rest_server.nbi_plugins.ietf_network_slice import register_ietf_nss +from .rest_server.nbi_plugins.ietf_inventory import register_ietf_inventory terminate = threading.Event() LOGGER = None @@ -64,6 +65,7 @@ def main(): register_etsi_bwm_api(rest_server) register_ietf_l2vpn(rest_server) # Registering L2VPN entrypoint register_ietf_nss(rest_server) # Registering NSS entrypoint + register_ietf_inventory(rest_server) # Registering inventories rest_server.start() # Wait for Ctrl+C or termination signal diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_inventory/Constants.py b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/Constants.py new file mode 100644 index 000000000..ed25dbab3 --- /dev/null +++ b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/Constants.py @@ -0,0 +1,91 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DEFAULT_MTU = 1512 +DEFAULT_ADDRESS_FAMILIES = ['IPV4'] +DEFAULT_BGP_AS = 65000 +DEFAULT_BGP_ROUTE_TARGET = '{:d}:{:d}'.format(DEFAULT_BGP_AS, 333) + +# TODO: improve definition of bearer mappings + +# Bearer mappings: +# device_uuid:endpoint_uuid => ( +# device_uuid, endpoint_uuid, router_id, route_dist, sub_if_index, +# address_ip, address_prefix, remote_router, circuit_id) + +BEARER_MAPPINGS = { + # OFC'22 + 'R1-EMU:13/1/2': ('R1-EMU', '13/1/2', '10.10.10.1', '65000:100', 400, '3.3.2.1', 24, None, None), + 'R2-EMU:13/1/2': ('R2-EMU', '13/1/2', '12.12.12.1', '65000:120', 450, '3.4.2.1', 24, None, None), + 'R3-EMU:13/1/2': ('R3-EMU', '13/1/2', '20.20.20.1', '65000:200', 500, '3.3.1.1', 24, None, None), + 'R4-EMU:13/1/2': ('R4-EMU', '13/1/2', '22.22.22.1', '65000:220', 550, '3.4.1.1', 24, None, None), + + # OECC/PSC'22 - domain 1 + 'R1@D1:3/1' : ('R1@D1', '3/1', '10.0.1.1', '65001:101', 100, '1.1.3.1', 24, None, None), + 'R1@D1:3/2' : ('R1@D1', '3/2', '10.0.1.1', '65001:101', 100, '1.1.3.2', 24, None, None), + 'R1@D1:3/3' : ('R1@D1', '3/3', '10.0.1.1', '65001:101', 100, '1.1.3.3', 24, None, None), + 'R2@D1:3/1' : ('R2@D1', '3/1', '10.0.1.2', '65001:102', 100, '1.2.3.1', 24, None, None), + 'R2@D1:3/2' : ('R2@D1', '3/2', '10.0.1.2', '65001:102', 100, '1.2.3.2', 24, None, None), + 'R2@D1:3/3' : ('R2@D1', '3/3', '10.0.1.2', '65001:102', 100, '1.2.3.3', 24, None, None), + 'R3@D1:3/1' : ('R3@D1', '3/1', '10.0.1.3', '65001:103', 100, '1.3.3.1', 24, None, None), + 'R3@D1:3/2' : ('R3@D1', '3/2', '10.0.1.3', '65001:103', 100, '1.3.3.2', 24, None, None), + 'R3@D1:3/3' : ('R3@D1', '3/3', '10.0.1.3', '65001:103', 100, '1.3.3.3', 24, None, None), + 'R4@D1:3/1' : ('R4@D1', '3/1', '10.0.1.4', '65001:104', 100, '1.4.3.1', 24, None, None), + 'R4@D1:3/2' : ('R4@D1', '3/2', '10.0.1.4', '65001:104', 100, '1.4.3.2', 24, None, None), + 'R4@D1:3/3' : ('R4@D1', '3/3', '10.0.1.4', '65001:104', 100, '1.4.3.3', 24, None, None), + + # OECC/PSC'22 - domain 2 + 'R1@D2:3/1' : ('R1@D2', '3/1', '10.0.2.1', '65002:101', 100, '2.1.3.1', 24, None, None), + 'R1@D2:3/2' : ('R1@D2', '3/2', '10.0.2.1', '65002:101', 100, '2.1.3.2', 24, None, None), + 'R1@D2:3/3' : ('R1@D2', '3/3', '10.0.2.1', '65002:101', 100, '2.1.3.3', 24, None, None), + 'R2@D2:3/1' : ('R2@D2', '3/1', '10.0.2.2', '65002:102', 100, '2.2.3.1', 24, None, None), + 'R2@D2:3/2' : ('R2@D2', '3/2', '10.0.2.2', '65002:102', 100, '2.2.3.2', 24, None, None), + 'R2@D2:3/3' : ('R2@D2', '3/3', '10.0.2.2', '65002:102', 100, '2.2.3.3', 24, None, None), + 'R3@D2:3/1' : ('R3@D2', '3/1', '10.0.2.3', '65002:103', 100, '2.3.3.1', 24, None, None), + 'R3@D2:3/2' : ('R3@D2', '3/2', '10.0.2.3', '65002:103', 100, '2.3.3.2', 24, None, None), + 'R3@D2:3/3' : ('R3@D2', '3/3', '10.0.2.3', '65002:103', 100, '2.3.3.3', 24, None, None), + 'R4@D2:3/1' : ('R4@D2', '3/1', '10.0.2.4', '65002:104', 100, '2.4.3.1', 24, None, None), + 'R4@D2:3/2' : ('R4@D2', '3/2', '10.0.2.4', '65002:104', 100, '2.4.3.2', 24, None, None), + 'R4@D2:3/3' : ('R4@D2', '3/3', '10.0.2.4', '65002:104', 100, '2.4.3.3', 24, None, None), + + # ECOC'22 + 'DC1-GW:CS1-GW1': ('CS1-GW1', '10/1', '5.5.1.1', None, 0, None, None, '5.5.2.1', 111), + 'DC1-GW:CS1-GW2': ('CS1-GW2', '10/1', '5.5.1.2', None, 0, None, None, '5.5.2.2', 222), + 'DC2-GW:CS2-GW1': ('CS2-GW1', '10/1', '5.5.2.1', None, 0, None, None, '5.5.1.1', 111), + 'DC2-GW:CS2-GW2': ('CS2-GW2', '10/1', '5.5.2.2', None, 0, None, None, '5.5.1.2', 222), + + # NetworkX'22 + 'R1:1/2': ('R1', '1/2', '5.1.1.2', None, 0, None, None, None, None), + 'R1:1/3': ('R1', '1/3', '5.1.1.3', None, 0, None, None, None, None), + 'R2:1/2': ('R2', '1/2', '5.2.1.2', None, 0, None, None, None, None), + 'R2:1/3': ('R2', '1/3', '5.2.1.3', None, 0, None, None, None, None), + 'R3:1/2': ('R3', '1/2', '5.3.1.2', None, 0, None, None, None, None), + 'R3:1/3': ('R3', '1/3', '5.3.1.3', None, 0, None, None, None, None), + 'R4:1/2': ('R4', '1/2', '5.4.1.2', None, 0, None, None, None, None), + 'R4:1/3': ('R4', '1/3', '5.4.1.3', None, 0, None, None, None, None), + + # OFC'23 + 'PE1:1/1': ('PE1', '1/1', '10.1.1.1', None, 0, None, None, None, None), + 'PE1:1/2': ('PE1', '1/2', '10.1.1.2', None, 0, None, None, None, None), + 'PE2:1/1': ('PE2', '1/1', '10.2.1.1', None, 0, None, None, None, None), + 'PE2:1/2': ('PE2', '1/2', '10.2.1.2', None, 0, None, None, None, None), + 'PE3:1/1': ('PE3', '1/1', '10.3.1.1', None, 0, None, None, None, None), + 'PE3:1/2': ('PE3', '1/2', '10.3.1.2', None, 0, None, None, None, None), + 'PE4:1/1': ('PE4', '1/1', '10.4.1.1', None, 0, None, None, None, None), + 'PE4:1/2': ('PE4', '1/2', '10.4.1.2', None, 0, None, None, None, None), + + 'R149:eth-1/0/22': ('R149', 'eth-1/0/22', '5.5.5.5', None, 0, None, None, '5.5.5.1', '100'), + 'R155:eth-1/0/22': ('R155', 'eth-1/0/22', '5.5.5.1', None, 0, None, None, '5.5.5.5', '100'), + 'R199:eth-1/0/21': ('R199', 'eth-1/0/21', '5.5.5.6', None, 0, None, None, '5.5.5.5', '100'), +} diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_inventory/DeviceClient.py b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/DeviceClient.py new file mode 100644 index 000000000..af0c96678 --- /dev/null +++ b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/DeviceClient.py @@ -0,0 +1,97 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import grpc, logging +from common.Constants import ServiceNameEnum +from common.Settings import get_service_host, get_service_port_grpc +from common.proto.context_pb2 import Device, DeviceConfig, DeviceId, Empty +from common.proto.device_pb2 import MonitoringSettings +from common.proto.device_pb2_grpc import DeviceServiceStub +from common.tools.client.RetryDecorator import retry, delay_exponential +from common.tools.grpc.Tools import grpc_message_to_json_string + +LOGGER = logging.getLogger(__name__) +MAX_RETRIES = 15 +DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) +RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, prepare_method_name='connect') + +class DeviceClient: + def __init__(self, host=None, port=None): + if not host: host = get_service_host(ServiceNameEnum.DEVICE) + if not port: port = get_service_port_grpc(ServiceNameEnum.DEVICE) + self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) + LOGGER.debug('Creating channel to {:s}...'.format(str(self.endpoint))) + self.channel = None + self.stub = None + self.connect() + LOGGER.debug('Channel created') + + def connect(self): + self.channel = grpc.insecure_channel(self.endpoint) + self.stub = DeviceServiceStub(self.channel) + + def close(self): + if self.channel is not None: self.channel.close() + self.channel = None + self.stub = None + + @RETRY_DECORATOR + def AddDevice(self, request : Device) -> DeviceId: + LOGGER.debug('AddDevice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.AddDevice(request) + LOGGER.debug('AddDevice result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def ConfigureDevice(self, request : Device) -> DeviceId: + LOGGER.debug('ConfigureDevice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.ConfigureDevice(request) + LOGGER.debug('ConfigureDevice result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def DeleteDevice(self, request : DeviceId) -> Empty: + LOGGER.debug('DeleteDevice request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.DeleteDevice(request) + LOGGER.debug('DeleteDevice result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def GetInitialConfig(self, request : DeviceId) -> DeviceConfig: + LOGGER.debug('GetInitialConfig request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetInitialConfig(request) + LOGGER.debug('GetInitialConfig result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def MonitorDeviceKpi(self, request : MonitoringSettings) -> Empty: + LOGGER.debug('MonitorDeviceKpi request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.MonitorDeviceKpi(request) + LOGGER.debug('MonitorDeviceKpi result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def DeviceInventory(self, request : MonitoringSettings) -> Empty: + LOGGER.debug('DeviceInventory request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.DeviceInventory(request) + LOGGER.debug('DeviceInventory result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + # @RETRY_DECORATOR + # def DeleteDevice(self, request : DeviceId) -> Empty: + # LOGGER.debug('DeleteDevice request: {:s}'.format(grpc_message_to_json_string(request))) + # response = self.stub.DeleteDevice(request) + # LOGGER.debug('DeleteDevice result: {:s}'.format(grpc_message_to_json_string(response))) + # return response + \ No newline at end of file diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_inventory/IETF_Inventories.py b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/IETF_Inventories.py new file mode 100644 index 000000000..afe5da558 --- /dev/null +++ b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/IETF_Inventories.py @@ -0,0 +1,60 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import Dict, List +from flask import request +from flask.json import jsonify +from flask_restful import Resource +from werkzeug.exceptions import UnsupportedMediaType +from common.Constants import DEFAULT_CONTEXT_NAME +from common.proto.context_pb2 import Device, DeviceConfig, DeviceId, Empty +from .DeviceClient import DeviceClient +from .schemas.device_inventory import SCHEMA_DEVICE_INVENTORY +from compute.service.rest_server.nbi_plugins.tools.HttpStatusCodes import HTTP_CREATED, HTTP_SERVERERROR +from compute.service.rest_server.nbi_plugins.tools.Validator import validate_message +from compute.service.rest_server.nbi_plugins.tools.Authentication import HTTP_AUTH + +LOGGER = logging.getLogger(__name__) + +class IETF_Inventories(Resource): + @HTTP_AUTH.login_required + def get(self): + return {} + + @HTTP_AUTH.login_required + def post(self): + if not request.is_json: raise UnsupportedMediaType('JSON payload is required') + request_data : Dict = request.json + LOGGER.debug('Request: {:s}'.format(str(request_data))) + validate_message(SCHEMA_DEVICE_INVENTORY, request_data) + + inventories : List[Dict] = request_data['ietf-inventory:dev-inventory'] + for inventory in inventories: + try: + # pylint: disable=no-member + inventory_request = Device() + inventory_request.slice_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME + inventory_request.slice_id.slice_uuid.uuid = inventory['component_uuid'] + + inventory_client = DeviceClient() + inventory_client.DeviceInventory() + + response = jsonify({}) + response.status_code = HTTP_CREATED + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Something went wrong Creating Service {:s}'.format(str(request))) + response = jsonify({'error': str(e)}) + response.status_code = HTTP_SERVERERROR + return response diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_inventory/IETF_Inventory.py b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/IETF_Inventory.py new file mode 100644 index 000000000..44de80c17 --- /dev/null +++ b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/IETF_Inventory.py @@ -0,0 +1,48 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from flask import request +from flask.json import jsonify +from flask_restful import Resource +from common.tools.context_queries.Device import get_device +from context.client.ContextClient import ContextClient +from ..tools.Authentication import HTTP_AUTH +from ..tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK, HTTP_SERVERERROR + +LOGGER = logging.getLogger(__name__) + +class IETF_Inventory(Resource): + @HTTP_AUTH.login_required + def get(self, device_id : str): + LOGGER.debug('Device_id: {:s}'.format(str(device_id))) + LOGGER.debug('Request: {:s}'.format(str(request))) + + try: + context_client = ContextClient() + + target = get_device(context_client, device_id, rw_copy=True) + if target is None: + raise Exception('Device({:s}) not found in database'.format(str(device_id))) + + if target.device_id.device_uuid.uuid != device_id: # pylint: disable=no-member + raise Exception('Inventory retrieval failed. Wrong Device Id was returned') + response = jsonify({}) + response.status_code = HTTP_OK + + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Something went wrong Retrieving Device Inventory({:s})'.format(str(device_id))) + response = jsonify({'error': str(e)}) + response.status_code = HTTP_SERVERERROR + return response diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_inventory/__init__.py b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/__init__.py new file mode 100644 index 000000000..3da6b3349 --- /dev/null +++ b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/__init__.py @@ -0,0 +1,33 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# RFC 8466 - L2VPN Service Model (L2SM) +# Ref: https://datatracker.ietf.org/doc/html/rfc8466 + +from flask_restful import Resource +from compute.service.rest_server.RestServer import RestServer +from .IETF_Inventories import IETF_Inventories +from .IETF_Inventory import IETF_Inventory + +URL_PREFIX = '/data/inventory' + +def _add_resource(rest_server : RestServer, resource : Resource, *urls, **kwargs): + urls = [(URL_PREFIX + url) for url in urls] + rest_server.add_resource(resource, *urls, **kwargs) + +def register_ietf_inventory(rest_server : RestServer): + _add_resource(rest_server, IETF_Inventories, + '/device-inventories') + _add_resource(rest_server, IETF_Inventory, + '/device-inventories/device-inventory=', '/device-inventories/device-inventory=') diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/Common.py b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/Common.py new file mode 100644 index 000000000..d708953ec --- /dev/null +++ b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/Common.py @@ -0,0 +1,16 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# String pattern for UUIDs such as '3fd942ee-2dc3-41d1-aeec-65aa85d117b2' +REGEX_UUID = r'[a-fA-F0-9]{8}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{12}' diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/__init__.py b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/__init__.py new file mode 100644 index 000000000..1549d9811 --- /dev/null +++ b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/device_inventory.py b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/device_inventory.py new file mode 100644 index 000000000..0bfe88c9b --- /dev/null +++ b/src/compute/service/rest_server/nbi_plugins/ietf_inventory/schemas/device_inventory.py @@ -0,0 +1,50 @@ +# Copyright 2022-2023 ETSI TeraFlowSDN - TFS OSG (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. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Example request: +# request = {'ietf-l2vpn-svc:vpn-service': [{ +# 'vpn-id': 'c6270231-f1de-4687-b2ed-7b58f9105775', +# 'vpn-svc-type': 'vpws', +# 'svc-topo': 'any-to-any', +# 'customer-name': 'osm' +# }]} + +from .Common import REGEX_UUID + +SCHEMA_DEVICE_INVENTORY = { + '$schema': 'https://json-schema.org/draft/2020-12/schema', + 'type': 'object', + 'properties': { + 'Component': { + 'type': 'array', + 'items': { + 'type': 'object', + 'required': ['component_uuid', 'name', 'type', 'attributes', 'parent'], + 'properties': { + 'component_uuid': {'type': 'string', 'pattern': REGEX_UUID}, + 'name': {'type': 'string'}, + 'type': {'type': 'string'}, + 'attributes': { + 'type': 'object', + 'patternProperties': { + '^.*$': {'type': 'string'}, + }, + }, + 'parent': {'type': 'string'}, + }, + }, + } + } +} + diff --git a/src/device/client/DeviceClient.py b/src/device/client/DeviceClient.py index b88727983..af0c96678 100644 --- a/src/device/client/DeviceClient.py +++ b/src/device/client/DeviceClient.py @@ -80,3 +80,18 @@ class DeviceClient: response = self.stub.MonitorDeviceKpi(request) LOGGER.debug('MonitorDeviceKpi result: {:s}'.format(grpc_message_to_json_string(response))) return response + + @RETRY_DECORATOR + def DeviceInventory(self, request : MonitoringSettings) -> Empty: + LOGGER.debug('DeviceInventory request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.DeviceInventory(request) + LOGGER.debug('DeviceInventory result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + # @RETRY_DECORATOR + # def DeleteDevice(self, request : DeviceId) -> Empty: + # LOGGER.debug('DeleteDevice request: {:s}'.format(grpc_message_to_json_string(request))) + # response = self.stub.DeleteDevice(request) + # LOGGER.debug('DeleteDevice result: {:s}'.format(grpc_message_to_json_string(response))) + # return response + \ No newline at end of file diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py index eeffdd7b0..f7f8dd251 100644 --- a/src/device/service/DeviceServiceServicerImpl.py +++ b/src/device/service/DeviceServiceServicerImpl.py @@ -33,7 +33,7 @@ from .ErrorMessages import ERROR_MISSING_DRIVER, ERROR_MISSING_KPI from .Tools import ( check_connect_rules, check_no_endpoints, compute_rules_to_add_delete, configure_rules, deconfigure_rules, get_device_controller_uuid, populate_config_rules, populate_endpoint_monitoring_resources, populate_endpoints, - populate_initial_config_rules, subscribe_kpi, unsubscribe_kpi, update_endpoints) + populate_initial_config_rules, subscribe_kpi, unsubscribe_kpi, update_endpoints, populate_inventory) LOGGER = logging.getLogger(__name__) @@ -115,7 +115,7 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): else: t_pop_config_rules = None - # TODO: populate components + # TODO: populate components Borrar components de la lista if len(errors) > 0: for error in errors: LOGGER.error(error) @@ -365,3 +365,31 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): return Empty() finally: self.mutex_queues.signal_done(device_uuid) + +@safe_and_metered_rpc_method(METRICS_POOL, LOGGER) +def DeviceInventory(self, request : DeviceId, context : grpc.ServicerContext) -> DeviceConfig: + device_uuid = request.device_uuid.uuid + + self.mutex_queues.wait_my_turn(device_uuid) + try: + context_client = ContextClient() + device = get_device( + context_client, device_uuid, rw_copy=False, include_endpoints=False, include_components=True, + include_config_rules=False) + if device is None: + raise NotFoundException('Device', device_uuid, extra_details='loading in DeleteDevice') + + driver : _Driver = get_driver(self.driver_instance_cache, device) + if driver is None: + msg = ERROR_MISSING_DRIVER.format(device_uuid=str(device_uuid)) + raise OperationFailedException('GetInitialConfig', extra_details=msg) + + device_config = DeviceConfig() + populate_inventory(device_uuid, device_config, driver) + + + + return device_config + finally: + self.mutex_queues.signal_done(device_uuid) + diff --git a/src/device/service/Tools.py b/src/device/service/Tools.py index b2b206471..71bbc6d19 100644 --- a/src/device/service/Tools.py +++ b/src/device/service/Tools.py @@ -21,7 +21,7 @@ from common.proto.device_pb2 import MonitoringSettings from common.proto.kpi_sample_types_pb2 import KpiSampleType from common.tools.grpc.ConfigRules import update_config_rule_custom from common.tools.grpc.Tools import grpc_message_to_json -from .driver_api._Driver import _Driver, RESOURCE_ENDPOINTS +from .driver_api._Driver import _Driver, RESOURCE_ENDPOINTS, RESOURCE_INVENTORY from .monitoring.MonitoringLoops import MonitoringLoops from .ErrorMessages import ( ERROR_BAD_RESOURCE, ERROR_DELETE, ERROR_GET, ERROR_GET_INIT, ERROR_MISSING_KPI, ERROR_SAMPLETYPE, ERROR_SET, @@ -434,3 +434,14 @@ def update_endpoints(src_device : Device, dst_device : Device) -> None: dst_topology_id = dst_endpoint_id.topology_id if len(src_topology_uuid) > 0: dst_topology_id.topology_uuid.uuid = src_topology_uuid if len(src_context_uuid) > 0: dst_topology_id.context_id.context_uuid.uuid = src_context_uuid + +def populate_inventory( + device : Device, driver : _Driver, monitoring_loops : MonitoringLoops, + new_sub_devices : Dict[str, Device], new_sub_links : Dict[str, Link] +) -> List[str]: + device_uuid = device.device_id.device_uuid.uuid + device_name = device.name + + resources_to_get = [RESOURCE_INVENTORY] + results_getInventory = driver.GetConfig(resources_to_get) + LOGGER.debug('results_get_inventory = {:s}'.format(str(results_getInventory))) -- GitLab