Loading src/device/service/drivers/openconfig/templates/Inventory.py +5 −1 Original line number Diff line number Diff line Loading @@ -76,6 +76,10 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]: if not component_location is None: add_value_from_tag(inventory['attributes'], 'location', component_location) component_id = xml_component.find('ocp:state/ocp:id', namespaces=NAMESPACES) if not component_id is None: add_value_from_tag(inventory['attributes'], 'id', component_id) component_type = xml_component.find('ocp:state/ocp:type', namespaces=NAMESPACES) if component_type is not None: component_type.text = component_type.text.replace('oc-platform-types:','') Loading Loading @@ -109,7 +113,7 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]: component_mfg_name = xml_component.find('ocp:state/ocp:mfg-name', namespaces=NAMESPACES) if not component_mfg_name is None: add_value_from_tag(inventory['attributes'], 'manufacturer-name', component_mfg_name) add_value_from_tag(inventory['attributes'], 'mfg-name', component_mfg_name) component_removable = xml_component.find('ocp:state/ocp:removable', namespaces=NAMESPACES) if not component_removable is None: Loading src/nbi/service/__main__.py +3 −0 Original line number Diff line number Diff line Loading @@ -18,9 +18,11 @@ from common.Constants import ServiceNameEnum from common.Settings import ( ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, get_metrics_port, wait_for_environment_variables) from .NbiService import NbiService from .rest_server.RestServer import RestServer from .rest_server.nbi_plugins.etsi_bwm import register_etsi_bwm_api from .rest_server.nbi_plugins.ietf_hardware import register_ietf_hardware from .rest_server.nbi_plugins.ietf_l2vpn import register_ietf_l2vpn from .rest_server.nbi_plugins.ietf_l3vpn import register_ietf_l3vpn from .rest_server.nbi_plugins.ietf_network import register_ietf_network Loading Loading @@ -63,6 +65,7 @@ def main(): rest_server = RestServer() register_etsi_bwm_api(rest_server) register_ietf_hardware(rest_server) register_ietf_l2vpn(rest_server) # Registering L2VPN entrypoint register_ietf_l3vpn(rest_server) # Registering L3VPN entrypoint register_ietf_network(rest_server) Loading src/nbi/service/rest_server/nbi_plugins/ietf_hardware/Hardware.py 0 → 100644 +53 −0 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI OSG/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. # 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_OK, HTTP_SERVERERROR from .YangHandler import YangHandler LOGGER = logging.getLogger(__name__) class Hardware(Resource): @HTTP_AUTH.login_required def get(self, device_uuid : str): LOGGER.debug('Device UUID: {:s}'.format(str(device_uuid))) LOGGER.debug('Request: {:s}'.format(str(request))) try: context_client = ContextClient() device = get_device( context_client, device_uuid, rw_copy=False, include_endpoints=False, include_config_rules=False, include_components=True ) if device is None: raise Exception('Device({:s}) not found in database'.format(str(device_uuid))) yang_handler = YangHandler() hardware_reply = yang_handler.compose(device) yang_handler.destroy() response = jsonify(hardware_reply) response.status_code = HTTP_OK except Exception as e: # pylint: disable=broad-except MSG = 'Something went wrong Retrieving Hardware of Device({:s})' LOGGER.exception(MSG.format(str(device_uuid))) response = jsonify({'error': str(e)}) response.status_code = HTTP_SERVERERROR return response No newline at end of file src/nbi/service/rest_server/nbi_plugins/ietf_hardware/YangHandler.py 0 → 100644 +132 −0 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI OSG/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. # 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 libyang, os from common.proto.context_pb2 import Device from typing import Dict, Optional import json import logging import re import datetime LOGGER = logging.getLogger(__name__) YANG_DIR = os.path.join(os.path.dirname(__file__), 'yang') YANG_MODULES = [ 'iana-hardware', 'ietf-hardware' ] class YangHandler: def __init__(self) -> None: self._yang_context = libyang.Context(YANG_DIR) for yang_module_name in YANG_MODULES: LOGGER.info('Loading module: {:s}'.format(str(yang_module_name))) self._yang_context.load_module(yang_module_name).feature_enable_all() def parse_to_dict(self, message : Dict) -> Dict: yang_module = self._yang_context.get_module('ietf-hardware') dnode : Optional[libyang.DNode] = yang_module.parse_data_dict( message, validate_present=True, validate=True, strict=True ) if dnode is None: raise Exception('Unable to parse Message({:s})'.format(str(message))) message = dnode.print_dict() dnode.free() return message @staticmethod def convert_to_iso_date(date_str: str) -> Optional[str]: date_str = date_str.strip('"') # Define the regex pattern for ISO 8601 date format pattern = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[\+\-]\d{2}:\d{2})" # Check if the input date string matches the pattern if re.match(pattern, date_str): return date_str # Already in ISO format else: try: # Parse the input date string as a datetime object datetime_obj = datetime.datetime.strptime(date_str, "%Y-%m-%d") # Convert to ISO format iso_date = datetime_obj.isoformat() + "Z" return iso_date except ValueError: return None # Invalid date format def compose(self, device : Device) -> Dict: # compose device iterating through the components hardware = self._yang_context.create_data_path('/ietf-hardware:hardware') physical_index = 1 for component in device.components: attributes = component.attributes component_new = hardware.create_path('component[name="{:s}"]'.format(component.name)) component_new.create_path('name', component.name) #Cambiar las clases especiales, su formato y añadir isfru component_type = component.type if component_type == "TRANSCEIVER" : component_type = "module" if component_type == "FRU" : component_type = "slack" component_new.create_path('is-fru', True) else : component_new.create_path('is-fru', False) component_type = component_type.replace("_", "-").lower() component_type = 'iana-hardware:' + component_type component_new.create_path('class', component_type) #Añadir resto de atributos en IETF physical_index += 1 component_new.create_path('physical-index', physical_index) component_new.create_path('description', attributes["description"]) component_new.create_path('parent', component.parent) if attributes["mfg-date"] != "": mfg_date = self.convert_to_iso_date(attributes["mfg-date"]) LOGGER.info('component[name="{:s}"]'.format(attributes["mfg-date"])) component_new.create_path('mfg-date', mfg_date) component_new.create_path('hardware-rev', attributes["hardware-rev"]) component_new.create_path('software-rev', attributes["software-rev"]) component_new.create_path('firmware-rev', attributes["firmware-version"]) component_new.create_path('serial-num', attributes["serial-num"]) component_new.create_path('mfg-name', attributes["mfg-name"]) if attributes["id"]: component_new.create_path('parent-rel-pos', attributes["id"]) component_new.create_path('uri', component.name) component_new.create_path('uuid', component.component_uuid.uuid) contains_child = [] for component2 in device.components: if component.name == component2.parent : contains_child.append(component2.name) component_new.create_path('contains-child', contains_child) return json.loads(hardware.print_mem('json')) def destroy(self) -> None: self._yang_context.destroy() No newline at end of file src/nbi/service/rest_server/nbi_plugins/ietf_hardware/__init__.py 0 → 100644 +21 −0 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI OSG/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. # 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. from nbi.service.rest_server.nbi_plugins.ietf_hardware.Hardware import Hardware from nbi.service.rest_server.RestServer import RestServer URL_PREFIX = "/restconf/data/device=<path:device_uuid>/ietf-hardware:hardware" def register_ietf_hardware(rest_server: RestServer): rest_server.add_resource(Hardware, URL_PREFIX) No newline at end of file Loading
src/device/service/drivers/openconfig/templates/Inventory.py +5 −1 Original line number Diff line number Diff line Loading @@ -76,6 +76,10 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]: if not component_location is None: add_value_from_tag(inventory['attributes'], 'location', component_location) component_id = xml_component.find('ocp:state/ocp:id', namespaces=NAMESPACES) if not component_id is None: add_value_from_tag(inventory['attributes'], 'id', component_id) component_type = xml_component.find('ocp:state/ocp:type', namespaces=NAMESPACES) if component_type is not None: component_type.text = component_type.text.replace('oc-platform-types:','') Loading Loading @@ -109,7 +113,7 @@ def parse(xml_data : ET.Element) -> List[Tuple[str, Dict[str, Any]]]: component_mfg_name = xml_component.find('ocp:state/ocp:mfg-name', namespaces=NAMESPACES) if not component_mfg_name is None: add_value_from_tag(inventory['attributes'], 'manufacturer-name', component_mfg_name) add_value_from_tag(inventory['attributes'], 'mfg-name', component_mfg_name) component_removable = xml_component.find('ocp:state/ocp:removable', namespaces=NAMESPACES) if not component_removable is None: Loading
src/nbi/service/__main__.py +3 −0 Original line number Diff line number Diff line Loading @@ -18,9 +18,11 @@ from common.Constants import ServiceNameEnum from common.Settings import ( ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, get_metrics_port, wait_for_environment_variables) from .NbiService import NbiService from .rest_server.RestServer import RestServer from .rest_server.nbi_plugins.etsi_bwm import register_etsi_bwm_api from .rest_server.nbi_plugins.ietf_hardware import register_ietf_hardware from .rest_server.nbi_plugins.ietf_l2vpn import register_ietf_l2vpn from .rest_server.nbi_plugins.ietf_l3vpn import register_ietf_l3vpn from .rest_server.nbi_plugins.ietf_network import register_ietf_network Loading Loading @@ -63,6 +65,7 @@ def main(): rest_server = RestServer() register_etsi_bwm_api(rest_server) register_ietf_hardware(rest_server) register_ietf_l2vpn(rest_server) # Registering L2VPN entrypoint register_ietf_l3vpn(rest_server) # Registering L3VPN entrypoint register_ietf_network(rest_server) Loading
src/nbi/service/rest_server/nbi_plugins/ietf_hardware/Hardware.py 0 → 100644 +53 −0 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI OSG/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. # 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_OK, HTTP_SERVERERROR from .YangHandler import YangHandler LOGGER = logging.getLogger(__name__) class Hardware(Resource): @HTTP_AUTH.login_required def get(self, device_uuid : str): LOGGER.debug('Device UUID: {:s}'.format(str(device_uuid))) LOGGER.debug('Request: {:s}'.format(str(request))) try: context_client = ContextClient() device = get_device( context_client, device_uuid, rw_copy=False, include_endpoints=False, include_config_rules=False, include_components=True ) if device is None: raise Exception('Device({:s}) not found in database'.format(str(device_uuid))) yang_handler = YangHandler() hardware_reply = yang_handler.compose(device) yang_handler.destroy() response = jsonify(hardware_reply) response.status_code = HTTP_OK except Exception as e: # pylint: disable=broad-except MSG = 'Something went wrong Retrieving Hardware of Device({:s})' LOGGER.exception(MSG.format(str(device_uuid))) response = jsonify({'error': str(e)}) response.status_code = HTTP_SERVERERROR return response No newline at end of file
src/nbi/service/rest_server/nbi_plugins/ietf_hardware/YangHandler.py 0 → 100644 +132 −0 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI OSG/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. # 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 libyang, os from common.proto.context_pb2 import Device from typing import Dict, Optional import json import logging import re import datetime LOGGER = logging.getLogger(__name__) YANG_DIR = os.path.join(os.path.dirname(__file__), 'yang') YANG_MODULES = [ 'iana-hardware', 'ietf-hardware' ] class YangHandler: def __init__(self) -> None: self._yang_context = libyang.Context(YANG_DIR) for yang_module_name in YANG_MODULES: LOGGER.info('Loading module: {:s}'.format(str(yang_module_name))) self._yang_context.load_module(yang_module_name).feature_enable_all() def parse_to_dict(self, message : Dict) -> Dict: yang_module = self._yang_context.get_module('ietf-hardware') dnode : Optional[libyang.DNode] = yang_module.parse_data_dict( message, validate_present=True, validate=True, strict=True ) if dnode is None: raise Exception('Unable to parse Message({:s})'.format(str(message))) message = dnode.print_dict() dnode.free() return message @staticmethod def convert_to_iso_date(date_str: str) -> Optional[str]: date_str = date_str.strip('"') # Define the regex pattern for ISO 8601 date format pattern = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[\+\-]\d{2}:\d{2})" # Check if the input date string matches the pattern if re.match(pattern, date_str): return date_str # Already in ISO format else: try: # Parse the input date string as a datetime object datetime_obj = datetime.datetime.strptime(date_str, "%Y-%m-%d") # Convert to ISO format iso_date = datetime_obj.isoformat() + "Z" return iso_date except ValueError: return None # Invalid date format def compose(self, device : Device) -> Dict: # compose device iterating through the components hardware = self._yang_context.create_data_path('/ietf-hardware:hardware') physical_index = 1 for component in device.components: attributes = component.attributes component_new = hardware.create_path('component[name="{:s}"]'.format(component.name)) component_new.create_path('name', component.name) #Cambiar las clases especiales, su formato y añadir isfru component_type = component.type if component_type == "TRANSCEIVER" : component_type = "module" if component_type == "FRU" : component_type = "slack" component_new.create_path('is-fru', True) else : component_new.create_path('is-fru', False) component_type = component_type.replace("_", "-").lower() component_type = 'iana-hardware:' + component_type component_new.create_path('class', component_type) #Añadir resto de atributos en IETF physical_index += 1 component_new.create_path('physical-index', physical_index) component_new.create_path('description', attributes["description"]) component_new.create_path('parent', component.parent) if attributes["mfg-date"] != "": mfg_date = self.convert_to_iso_date(attributes["mfg-date"]) LOGGER.info('component[name="{:s}"]'.format(attributes["mfg-date"])) component_new.create_path('mfg-date', mfg_date) component_new.create_path('hardware-rev', attributes["hardware-rev"]) component_new.create_path('software-rev', attributes["software-rev"]) component_new.create_path('firmware-rev', attributes["firmware-version"]) component_new.create_path('serial-num', attributes["serial-num"]) component_new.create_path('mfg-name', attributes["mfg-name"]) if attributes["id"]: component_new.create_path('parent-rel-pos', attributes["id"]) component_new.create_path('uri', component.name) component_new.create_path('uuid', component.component_uuid.uuid) contains_child = [] for component2 in device.components: if component.name == component2.parent : contains_child.append(component2.name) component_new.create_path('contains-child', contains_child) return json.loads(hardware.print_mem('json')) def destroy(self) -> None: self._yang_context.destroy() No newline at end of file
src/nbi/service/rest_server/nbi_plugins/ietf_hardware/__init__.py 0 → 100644 +21 −0 Original line number Diff line number Diff line # Copyright 2022-2024 ETSI OSG/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. # 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. from nbi.service.rest_server.nbi_plugins.ietf_hardware.Hardware import Hardware from nbi.service.rest_server.RestServer import RestServer URL_PREFIX = "/restconf/data/device=<path:device_uuid>/ietf-hardware:hardware" def register_ietf_hardware(rest_server: RestServer): rest_server.add_resource(Hardware, URL_PREFIX) No newline at end of file