From 2c8c18efb566b65f484db9ffc8b2d297a4fd95ea Mon Sep 17 00:00:00 2001 From: armingol Date: Mon, 1 Jun 2026 09:38:38 +0000 Subject: [PATCH 1/9] feat: add custom IPoWDM device driver with synthetic sampling capabilities --- proto/context.proto | 1 + .../drivers/custom_ipowdm/Constants.py | 21 ++ .../custom_ipowdm/CustomIpowdmDriver.py | 334 ++++++++++++++++++ .../SyntheticSamplingParameters.py | 86 +++++ .../service/drivers/custom_ipowdm/Tools.py | 134 +++++++ .../service/drivers/custom_ipowdm/__init__.py | 13 + 6 files changed, 589 insertions(+) create mode 100644 src/device/service/drivers/custom_ipowdm/Constants.py create mode 100644 src/device/service/drivers/custom_ipowdm/CustomIpowdmDriver.py create mode 100644 src/device/service/drivers/custom_ipowdm/SyntheticSamplingParameters.py create mode 100644 src/device/service/drivers/custom_ipowdm/Tools.py create mode 100644 src/device/service/drivers/custom_ipowdm/__init__.py diff --git a/proto/context.proto b/proto/context.proto index 0625d0440..0d7720f2b 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -255,6 +255,7 @@ enum DeviceDriverEnum { DEVICEDRIVER_GNMI_NOKIA_SRLINUX = 19; DEVICEDRIVER_OPENROADM = 20; DEVICEDRIVER_RESTCONF_OPENCONFIG = 21; + DEVICEDRIVER_CUSTOM_IPOWDM = 22; } enum DeviceOperationalStatusEnum { diff --git a/src/device/service/drivers/custom_ipowdm/Constants.py b/src/device/service/drivers/custom_ipowdm/Constants.py new file mode 100644 index 000000000..51ac1c483 --- /dev/null +++ b/src/device/service/drivers/custom_ipowdm/Constants.py @@ -0,0 +1,21 @@ +# Copyright 2022-2026 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. +# 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 device.service.driver_api._Driver import RESOURCE_ENDPOINTS, RESOURCE_INTERFACES, RESOURCE_NETWORK_INSTANCES + +SPECIAL_RESOURCE_MAPPINGS = { + RESOURCE_ENDPOINTS : '/endpoints', + RESOURCE_INTERFACES : '/interfaces', + RESOURCE_NETWORK_INSTANCES: '/net-instances', +} diff --git a/src/device/service/drivers/custom_ipowdm/CustomIpowdmDriver.py b/src/device/service/drivers/custom_ipowdm/CustomIpowdmDriver.py new file mode 100644 index 000000000..f1f583382 --- /dev/null +++ b/src/device/service/drivers/custom_ipowdm/CustomIpowdmDriver.py @@ -0,0 +1,334 @@ +# Copyright 2022-2026 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. +# 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 anytree, json, logging, pytz, queue, re, threading +from datetime import datetime, timedelta +from typing import Any, Iterator, List, Optional, Tuple, Union +from apscheduler.executors.pool import ThreadPoolExecutor +from apscheduler.job import Job +from apscheduler.jobstores.memory import MemoryJobStore +from apscheduler.schedulers.background import BackgroundScheduler +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.type_checkers.Checkers import chk_float, chk_length, chk_string, chk_type +from device.service.driver_api._Driver import _Driver +from device.service.driver_api.AnyTreeTools import TreeNode, dump_subtree, get_subnode, set_subnode_value +from .Constants import SPECIAL_RESOURCE_MAPPINGS +from .SyntheticSamplingParameters import SyntheticSamplingParameters, do_sampling +from .Tools import compose_resource_endpoint, connect_to_xr_agent +import requests + + +LOGGER = logging.getLogger(__name__) + +RE_GET_ENDPOINT_FROM_INTERFACE = re.compile(r'^\/interface\[([^\]]+)\].*') + +DRIVER_NAME = 'custom_ipowdm' +METRICS_POOL = MetricsPool('Device', 'Driver', labels={'driver': DRIVER_NAME}) + +class CustomIpowdmDriver(_Driver): + def __init__(self, address : str, port : int, **settings) -> None: + super().__init__(DRIVER_NAME, address, port, **settings) + self.__lock = threading.Lock() + self.__address = address + self.__initial = TreeNode('.') + self.__running = TreeNode('.') + self.__subscriptions = TreeNode('.') + + endpoints = self.settings.get('endpoints', []) + endpoint_resources = [] + for endpoint in endpoints: + endpoint_resource = compose_resource_endpoint(endpoint) + if endpoint_resource is None: continue + endpoint_resources.append(endpoint_resource) + self.SetConfig(endpoint_resources) + + self.__started = threading.Event() + self.__terminate = threading.Event() + self.__scheduler = BackgroundScheduler(daemon=True) # scheduler used to emulate sampling events + self.__scheduler.configure( + jobstores = {'default': MemoryJobStore()}, + executors = {'default': ThreadPoolExecutor(max_workers=1)}, + job_defaults = {'coalesce': False, 'max_instances': 3}, + timezone=pytz.utc) + self.__out_samples = queue.Queue() + self.__synthetic_sampling_parameters = SyntheticSamplingParameters() + + def Connect(self) -> bool: + # If started, assume it is already connected + if self.__started.is_set(): return True + + # Connect triggers activation of sampling events that will be scheduled based on subscriptions + self.__scheduler.start() + + # Indicate the driver is now connected to the device + self.__started.set() + return True + + def Disconnect(self) -> bool: + # Trigger termination of loops and processes + self.__terminate.set() + + # If not started, assume it is already disconnected + if not self.__started.is_set(): return True + + # Disconnect triggers deactivation of sampling events + self.__scheduler.shutdown() + return True + + @metered_subclass_method(METRICS_POOL) + def GetInitialConfig(self) -> List[Tuple[str, Any]]: + with self.__lock: + return dump_subtree(self.__initial) + + @metered_subclass_method(METRICS_POOL) + def GetConfig(self, resource_keys : List[str] = []) -> List[Tuple[str, Union[Any, None, Exception]]]: + chk_type('resources', resource_keys, list) + with self.__lock: + if len(resource_keys) == 0: return dump_subtree(self.__running) + results = [] + resolver = anytree.Resolver(pathattr='name') + for i,resource_key in enumerate(resource_keys): + str_resource_name = 'resource_key[#{:d}]'.format(i) + try: + chk_string(str_resource_name, resource_key, allow_empty=False) + resource_key = SPECIAL_RESOURCE_MAPPINGS.get(resource_key, resource_key) + resource_path = resource_key.split('/') + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Exception validating {:s}: {:s}'.format(str_resource_name, str(resource_key))) + results.append((resource_key, e)) # if validation fails, store the exception + continue + + resource_node = get_subnode(resolver, self.__running, resource_path, default=None) + # if not found, resource_node is None + if resource_node is None: continue + results.extend(dump_subtree(resource_node)) + return results + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + results = [] + if 'ipowdm_ruleset' in str(resources): + connect_to_xr_agent(resources) + results.append(True) + else: + resolver = anytree.Resolver(pathattr='name') + with self.__lock: + for i,resource in enumerate(resources): + str_resource_name = 'resources[#{:d}]'.format(i) + try: + chk_type(str_resource_name, resource, (list, tuple)) + chk_length(str_resource_name, resource, min_length=2, max_length=2) + resource_key,resource_value = resource + chk_string(str_resource_name, resource_key, allow_empty=False) + resource_path = resource_key.split('/') + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Exception validating {:s}: {:s}'.format(str_resource_name, str(resource_key))) + results.append(e) # if validation fails, store the exception + continue + try: + resource_value = json.loads(resource_value) + except: # pylint: disable=bare-except + pass + + if resource_key.startswith('/ipowdm/service/'): + LOGGER.info('[%s] Legacy IPoWDM Service Provisioning: Payload=%s', self.__address, str(resource_value)) + elif resource_key.startswith('/ipowdm/l3nm/'): + LOGGER.info('[%s] L3NM Service Provisioning: Payload=%s', self.__address, str(resource_value)) + elif resource_key.startswith('/ipowdm/pluggables/'): + LOGGER.info('[%s] Pluggables Service Provisioning: Payload=%s', self.__address, str(resource_value)) + try: + key_parts = resource_key.split('/') + if len(key_parts) >= 5: + service_uuid_raw = key_parts[3] + # Normalize UUID: if serviceId contains '-pluggable-', take the part before it + if "-pluggable-" in service_uuid_raw: + service_uuid = service_uuid_raw.split("-pluggable-")[0] + else: + service_uuid = service_uuid_raw + + if not hasattr(EmulatedDriver, 'pluggables_pending'): + EmulatedDriver.pluggables_pending = {} + + if service_uuid in EmulatedDriver.pluggables_pending: + stored_payload = EmulatedDriver.pluggables_pending.pop(service_uuid) + current_payload = resource_value + + def format_entry(payload): + config = payload.get('config', {}) + return { + 'uuid': payload.get('device'), + 'power': config.get('target-output-power', 0.0), + 'frequency': config.get('frequency', 0.0) + } + combined_data = [ + {'src': format_entry(stored_payload)}, + {'dst': format_entry(current_payload)} + ] + LOGGER.info('[%s] Pluggables Service Provisioning Aggregated: %s', self.__address, json.dumps(combined_data, indent=2)) + # TODO Dynamic IP + url = "http://192.168.88.17:9849/api-v0/transponders" + headers = {'Content-Type': 'application/json'} + response = requests.post(url, json=combined_data, headers=headers) + LOGGER.info('[%s] Pluggables Service Provisioning Response: %s', self.__address, str(response.text)) + else: + EmulatedDriver.pluggables_pending[service_uuid] = resource_value + LOGGER.debug('[%s] Pluggables Service Partial Provisioning stored for %s', self.__address, service_uuid) + except Exception as e: + LOGGER.warning("Error processing Pluggables aggregation: %s", str(e)) + LOGGER.info('[%s] Pluggables Service Provisioning: Payload=%s', self.__address, str(resource_value)) + + set_subnode_value(resolver, self.__running, resource_path, resource_value) + + match = RE_GET_ENDPOINT_FROM_INTERFACE.match(resource_key) + if match is not None: + endpoint_uuid = match.group(1) + if '.' in endpoint_uuid: endpoint_uuid = endpoint_uuid.split('.')[0] + self.__synthetic_sampling_parameters.set_endpoint_configured(endpoint_uuid) + + results.append(True) + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig(self, resources : List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: + chk_type('resources', resources, list) + if len(resources) == 0: return [] + results = [] + resolver = anytree.Resolver(pathattr='name') + with self.__lock: + for i,resource in enumerate(resources): + str_resource_name = 'resources[#{:d}]'.format(i) + try: + chk_type(str_resource_name, resource, (list, tuple)) + chk_length(str_resource_name, resource, min_length=2, max_length=2) + resource_key,_ = resource + chk_string(str_resource_name, resource_key, allow_empty=False) + resource_path = resource_key.split('/') + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Exception validating {:s}: {:s}'.format(str_resource_name, str(resource_key))) + results.append(e) # if validation fails, store the exception + continue + + resource_node = get_subnode(resolver, self.__running, resource_path, default=None) + # if not found, resource_node is None + if resource_node is None: + results.append(False) + continue + + match = RE_GET_ENDPOINT_FROM_INTERFACE.match(resource_key) + if match is not None: + endpoint_uuid = match.group(1) + if '.' in endpoint_uuid: endpoint_uuid = endpoint_uuid.split('.')[0] + self.__synthetic_sampling_parameters.unset_endpoint_configured(endpoint_uuid) + + parent = resource_node.parent + children = list(parent.children) + children.remove(resource_node) + parent.children = tuple(children) + results.append(True) + return results + + @metered_subclass_method(METRICS_POOL) + def SubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + chk_type('subscriptions', subscriptions, list) + if len(subscriptions) == 0: return [] + results = [] + resolver = anytree.Resolver(pathattr='name') + with self.__lock: + for i,subscription in enumerate(subscriptions): + str_subscription_name = 'subscriptions[#{:d}]'.format(i) + try: + chk_type(str_subscription_name, subscription, (list, tuple)) + chk_length(str_subscription_name, subscription, min_length=3, max_length=3) + resource_key,sampling_duration,sampling_interval = subscription + chk_string(str_subscription_name + '.resource_key', resource_key, allow_empty=False) + resource_path = resource_key.split('/') + chk_float(str_subscription_name + '.sampling_duration', sampling_duration, min_value=0) + chk_float(str_subscription_name + '.sampling_interval', sampling_interval, min_value=0) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Exception validating {:s}: {:s}'.format(str_subscription_name, str(resource_key))) + results.append(e) # if validation fails, store the exception + continue + + start_date,end_date = None,None + if sampling_duration <= 1.e-12: + start_date = datetime.utcnow() + end_date = start_date + timedelta(seconds=sampling_duration) + + job_id = 'k={:s}/d={:f}/i={:f}'.format(resource_key, sampling_duration, sampling_interval) + job = self.__scheduler.add_job( + do_sampling, args=(self.__synthetic_sampling_parameters, resource_key, self.__out_samples), + kwargs={}, id=job_id, trigger='interval', seconds=sampling_interval, start_date=start_date, + end_date=end_date, timezone=pytz.utc) + + subscription_path = resource_path + ['{:.3f}:{:.3f}'.format(sampling_duration, sampling_interval)] + set_subnode_value(resolver, self.__subscriptions, subscription_path, job) + results.append(True) + return results + + @metered_subclass_method(METRICS_POOL) + def UnsubscribeState(self, subscriptions : List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + chk_type('subscriptions', subscriptions, list) + if len(subscriptions) == 0: return [] + results = [] + resolver = anytree.Resolver(pathattr='name') + with self.__lock: + for i,resource in enumerate(subscriptions): + str_subscription_name = 'resources[#{:d}]'.format(i) + try: + chk_type(str_subscription_name, resource, (list, tuple)) + chk_length(str_subscription_name, resource, min_length=3, max_length=3) + resource_key,sampling_duration,sampling_interval = resource + chk_string(str_subscription_name + '.resource_key', resource_key, allow_empty=False) + resource_path = resource_key.split('/') + chk_float(str_subscription_name + '.sampling_duration', sampling_duration, min_value=0) + chk_float(str_subscription_name + '.sampling_interval', sampling_interval, min_value=0) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception('Exception validating {:s}: {:s}'.format(str_subscription_name, str(resource_key))) + results.append(e) # if validation fails, store the exception + continue + + subscription_path = resource_path + ['{:.3f}:{:.3f}'.format(sampling_duration, sampling_interval)] + subscription_node = get_subnode(resolver, self.__subscriptions, subscription_path) + + # if not found, resource_node is None + if subscription_node is None: + results.append(False) + continue + + job : Job = getattr(subscription_node, 'value', None) + if job is None or not isinstance(job, Job): + raise Exception('Malformed subscription node or wrong resource key: {:s}'.format(str(resource))) + job.remove() + + parent = subscription_node.parent + children = list(parent.children) + children.remove(subscription_node) + parent.children = tuple(children) + + results.append(True) + return results + + def GetState(self, blocking=False, terminate : Optional[threading.Event] = None) -> Iterator[Tuple[str, Any]]: + while True: + if self.__terminate.is_set(): break + if terminate is not None and terminate.is_set(): break + try: + sample = self.__out_samples.get(block=blocking, timeout=0.1) + except queue.Empty: + if blocking: continue + return + if sample is None: continue + yield sample diff --git a/src/device/service/drivers/custom_ipowdm/SyntheticSamplingParameters.py b/src/device/service/drivers/custom_ipowdm/SyntheticSamplingParameters.py new file mode 100644 index 000000000..52584a345 --- /dev/null +++ b/src/device/service/drivers/custom_ipowdm/SyntheticSamplingParameters.py @@ -0,0 +1,86 @@ +# Copyright 2022-2026 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. +# 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, math, queue, random, re, threading +from datetime import datetime +from typing import Optional, Tuple + +LOGGER = logging.getLogger(__name__) + +RE_GET_ENDPOINT_METRIC = re.compile(r'.*\/endpoint\[([^\]]+)\]\/state\/(.*)') + +MSG_ERROR_PARSE = '[get] unable to extract endpoint-metric from monitoring_resource_key "{:s}"' +MSG_INFO = '[get] monitoring_resource_key={:s}, endpoint_uuid={:s}, metric={:s}, metric_sense={:s}' + +class SyntheticSamplingParameters: + def __init__(self) -> None: + self.__lock = threading.Lock() + self.__data = {} + self.__configured_endpoints = set() + + def set_endpoint_configured(self, endpoint_uuid : str): + with self.__lock: + self.__configured_endpoints.add(endpoint_uuid) + + def unset_endpoint_configured(self, endpoint_uuid : str): + with self.__lock: + self.__configured_endpoints.discard(endpoint_uuid) + + def get(self, monitoring_resource_key : str) -> Optional[Tuple[float, float, float, float, float]]: + with self.__lock: + match = RE_GET_ENDPOINT_METRIC.match(monitoring_resource_key) + if match is None: + LOGGER.error(MSG_ERROR_PARSE.format(monitoring_resource_key)) + return None + endpoint_uuid = match.group(1) + + # If endpoint is not configured, generate a flat synthetic traffic aligned at 0 + if endpoint_uuid not in self.__configured_endpoints: return (0, 0, 1, 0, 0) + + metric = match.group(2) + metric_sense = metric.lower().replace('packets_', '').replace('bytes_', '') + + LOGGER.debug(MSG_INFO.format(monitoring_resource_key, endpoint_uuid, metric, metric_sense)) + + parameters_key = '{:s}-{:s}'.format(endpoint_uuid, metric_sense) + parameters = self.__data.get(parameters_key) + if parameters is not None: return parameters + + # assume packets + amplitude = 1.e7 * random.random() + phase = 60 * random.random() + period = 3600 * random.random() + offset = 1.e8 * random.random() + amplitude + avg_bytes_per_packet = random.randint(500, 1500) + parameters = (amplitude, phase, period, offset, avg_bytes_per_packet) + return self.__data.setdefault(parameters_key, parameters) + +def do_sampling( + synthetic_sampling_parameters : SyntheticSamplingParameters, monitoring_resource_key : str, + out_samples : queue.Queue +) -> None: + parameters = synthetic_sampling_parameters.get(monitoring_resource_key) + if parameters is None: return + amplitude, phase, period, offset, avg_bytes_per_packet = parameters + + if 'bytes' in monitoring_resource_key.lower(): + # convert to bytes + amplitude = avg_bytes_per_packet * amplitude + offset = avg_bytes_per_packet * offset + + timestamp = datetime.timestamp(datetime.utcnow()) + waveform = amplitude * math.sin(2 * math.pi * timestamp / period + phase) + offset + noise = amplitude * random.random() + value = abs(0.95 * waveform + 0.05 * noise) + out_samples.put_nowait((timestamp, monitoring_resource_key, value)) diff --git a/src/device/service/drivers/custom_ipowdm/Tools.py b/src/device/service/drivers/custom_ipowdm/Tools.py new file mode 100644 index 000000000..89f7724f9 --- /dev/null +++ b/src/device/service/drivers/custom_ipowdm/Tools.py @@ -0,0 +1,134 @@ +# 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. +# 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 json +import logging +import requests +from common.proto.kpi_sample_types_pb2 import KpiSampleType +from common.type_checkers.Checkers import chk_attribute, chk_string, chk_type +from device.service.driver_api._Driver import RESOURCE_ENDPOINTS +from .Constants import SPECIAL_RESOURCE_MAPPINGS +from typing import Any, Dict, Optional, Tuple + +LOGGER = logging.getLogger(__name__) + +def process_optional_string_field( + endpoint_data : Dict[str, Any], field_name : str, endpoint_resource_value : Dict[str, Any] +) -> None: + field_value = chk_attribute(field_name, endpoint_data, 'endpoint_data', default=None) + if field_value is None: return + chk_string('endpoint_data.{:s}'.format(field_name), field_value) + if len(field_value) > 0: endpoint_resource_value[field_name] = field_value + +def compose_resource_endpoint(endpoint_data : Dict[str, Any]) -> Optional[Tuple[str, Dict]]: + try: + # Check type of endpoint_data + chk_type('endpoint_data', endpoint_data, dict) + + # Check endpoint UUID (mandatory) + endpoint_uuid = chk_attribute('uuid', endpoint_data, 'endpoint_data') + chk_string('endpoint_data.uuid', endpoint_uuid, min_length=1) + endpoint_resource_path = SPECIAL_RESOURCE_MAPPINGS.get(RESOURCE_ENDPOINTS) + endpoint_resource_key = '{:s}/endpoint[{:s}]'.format(endpoint_resource_path, endpoint_uuid) + endpoint_resource_value = {'uuid': endpoint_uuid} + + # Check endpoint optional string fields + process_optional_string_field(endpoint_data, 'name', endpoint_resource_value) + process_optional_string_field(endpoint_data, 'type', endpoint_resource_value) + process_optional_string_field(endpoint_data, 'context_uuid', endpoint_resource_value) + process_optional_string_field(endpoint_data, 'topology_uuid', endpoint_resource_value) + + # Check endpoint sample types (optional) + endpoint_sample_types = chk_attribute('sample_types', endpoint_data, 'endpoint_data', default=[]) + chk_type('endpoint_data.sample_types', endpoint_sample_types, list) + sample_types = {} + sample_type_errors = [] + for i,endpoint_sample_type in enumerate(endpoint_sample_types): + field_name = 'endpoint_data.sample_types[{:d}]'.format(i) + try: + chk_type(field_name, endpoint_sample_type, (int, str)) + if isinstance(endpoint_sample_type, int): + metric_name = KpiSampleType.Name(endpoint_sample_type) + metric_id = endpoint_sample_type + elif isinstance(endpoint_sample_type, str): + metric_id = KpiSampleType.Value(endpoint_sample_type) + metric_name = endpoint_sample_type + else: + str_type = str(type(endpoint_sample_type)) + raise Exception('Bad format: {:s}'.format(str_type)) # pylint: disable=broad-exception-raised + except Exception as e: # pylint: disable=broad-exception-caught + MSG = 'Unsupported {:s}({:s}) : {:s}' + sample_type_errors.append(MSG.format(field_name, str(endpoint_sample_type), str(e))) + + metric_name = metric_name.lower().replace('kpisampletype_', '') + monitoring_resource_key = '{:s}/state/{:s}'.format(endpoint_resource_key, metric_name) + sample_types[metric_id] = monitoring_resource_key + + if len(sample_type_errors) > 0: + # pylint: disable=broad-exception-raised + raise Exception('Malformed Sample Types:\n{:s}'.format('\n'.join(sample_type_errors))) + + if len(sample_types) > 0: + endpoint_resource_value['sample_types'] = sample_types + + if 'location' in endpoint_data: + endpoint_resource_value['location'] = endpoint_data['location'] + + if "site_location" in endpoint_data: + endpoint_resource_value["site_location"] = endpoint_data["site_location"] + + if "ce-ip" in endpoint_data: + endpoint_resource_value["ce-ip"] = endpoint_data["ce-ip"] + + if "address_ip" in endpoint_data: + endpoint_resource_value["address_ip"] = endpoint_data["address_ip"] + + if "address_prefix" in endpoint_data: + endpoint_resource_value["address_prefix"] = endpoint_data["address_prefix"] + + if "mtu" in endpoint_data: + endpoint_resource_value["mtu"] = endpoint_data["mtu"] + + if "ipv4_lan_prefixes" in endpoint_data: + endpoint_resource_value["ipv4_lan_prefixes"] = endpoint_data[ + "ipv4_lan_prefixes" + ] + + return endpoint_resource_key, endpoint_resource_value + except: # pylint: disable=bare-except + LOGGER.exception('Problem composing endpoint({:s})'.format(str(endpoint_data))) + return None + +# TODO Dynamic IP +def connect_to_xr_agent(resources): + rule_set = resources[0][1]['rule_set'] + nodes = [ + {'src': { + 'uuid': rule_set['src'][0]['uuid'], + 'power': rule_set['src'][0]['power'], + 'frequency': rule_set['src'][0]['frequency'] + }}, + {'dst': { + 'uuid': rule_set['dst'][0]['uuid'], + 'power': rule_set['dst'][0]['power'], + 'frequency': rule_set['dst'][0]['frequency'] + }} + ] + url = "http://192.168.88.17:9849/api-v0/transponders" + headers = { + "Content-Type": "application/json", + "Expect": "" + } + json_data = json.dumps(nodes) + requests.post(url, data=json_data, headers=headers, timeout=10) diff --git a/src/device/service/drivers/custom_ipowdm/__init__.py b/src/device/service/drivers/custom_ipowdm/__init__.py new file mode 100644 index 000000000..b53987a4e --- /dev/null +++ b/src/device/service/drivers/custom_ipowdm/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2026 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. +# 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. -- GitLab From c3e5b99326b08a2a9497e95a6f6927da430212c9 Mon Sep 17 00:00:00 2001 From: armingol Date: Mon, 1 Jun 2026 13:43:59 +0000 Subject: [PATCH 2/9] Instanciate Device driver CUSTOM_IPOWDM --- src/common/type_checkers/Assertions.py | 3 ++- .../service/database/models/enums/DeviceDriver.py | 1 + src/device/service/drivers/__init__.py | 11 +++++++++++ src/device/service/drivers/ietf_l2vpn/TfsApiClient.py | 1 + src/device/service/drivers/ietf_l3vpn/TfsApiClient.py | 1 + src/device/service/drivers/ietf_slice/TfsApiClient.py | 1 + .../service/drivers/optical_tfs/TfsApiClient.py | 1 + .../service/drivers/transport_api/TfsApiClient.py | 4 ++++ .../src/main/java/org/etsi/tfs/policy/Serializer.java | 2 ++ .../test/java/org/etsi/tfs/policy/SerializerTest.java | 6 +++++- .../grpc/context/ContextOuterClass.java | 11 +++++++++++ src/webui/service/device/routes.py | 2 ++ .../src/main/java/org/etsi/tfs/ztp/Serializer.java | 4 ++++ .../test/java/org/etsi/tfs/ztp/SerializerTest.java | 2 ++ .../grpc/context/ContextOuterClass.java | 11 +++++++++++ 15 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py index d6f207849..7c6b9d79b 100644 --- a/src/common/type_checkers/Assertions.py +++ b/src/common/type_checkers/Assertions.py @@ -59,7 +59,8 @@ def validate_device_driver_enum(message): 'DEVICEDRIVER_RYU', 'DEVICEDRIVER_GNMI_NOKIA_SRLINUX', 'DEVICEDRIVER_OPENROADM', - 'DEVICEDRIVER_GNMI_OPENCONFIG', + 'DEVICEDRIVER_RESTCONF_OPENCONFIG', + 'DEVICEDRIVER_CUSTOM_IPOWDM', ] def validate_device_operational_status_enum(message): diff --git a/src/context/service/database/models/enums/DeviceDriver.py b/src/context/service/database/models/enums/DeviceDriver.py index 5c228c87e..d029af443 100644 --- a/src/context/service/database/models/enums/DeviceDriver.py +++ b/src/context/service/database/models/enums/DeviceDriver.py @@ -44,6 +44,7 @@ class ORM_DeviceDriverEnum(enum.Enum): GNMI_NOKIA_SRLINUX = DeviceDriverEnum.DEVICEDRIVER_GNMI_NOKIA_SRLINUX OPENROADM = DeviceDriverEnum.DEVICEDRIVER_OPENROADM RESTCONF_OPENCONFIG = DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG + CUSTOM_IPOWDM = DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM grpc_to_enum__device_driver = functools.partial( grpc_to_enum, DeviceDriverEnum, ORM_DeviceDriverEnum) diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index 85ea4bef0..95d405e05 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -270,3 +270,14 @@ if LOAD_ALL_DEVICE_DRIVERS: FilterFieldEnum.DRIVER : DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG, } ])) +if LOAD_ALL_DEVICE_DRIVERS: + from .custom_ipowdm.CustomIPoWDMDriver import CustomIPoWDMDriver # pylint: disable=wrong-import-position + DRIVERS.append( + (CustomIPoWDMDriver, [ + { + FilterFieldEnum.DEVICE_TYPE: [ + DeviceTypeEnum.PACKET_ROUTER + ], + FilterFieldEnum.DRIVER : DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM, + } + ])) \ No newline at end of file diff --git a/src/device/service/drivers/ietf_l2vpn/TfsApiClient.py b/src/device/service/drivers/ietf_l2vpn/TfsApiClient.py index aeb4132bd..88ec07a09 100644 --- a/src/device/service/drivers/ietf_l2vpn/TfsApiClient.py +++ b/src/device/service/drivers/ietf_l2vpn/TfsApiClient.py @@ -51,6 +51,7 @@ MAPPING_DRIVER = { 'DEVICEDRIVER_GNMI_NOKIA_SRLINUX' : 19, 'DEVICEDRIVER_OPENROADM' : 20, 'DEVICEDRIVER_RESTCONF_OPENCONFIG' : 21, + 'DEVICEDRIVER_CUSTOM_IPOWDM' : 22, } diff --git a/src/device/service/drivers/ietf_l3vpn/TfsApiClient.py b/src/device/service/drivers/ietf_l3vpn/TfsApiClient.py index 1ada5ddef..947b3ca5c 100644 --- a/src/device/service/drivers/ietf_l3vpn/TfsApiClient.py +++ b/src/device/service/drivers/ietf_l3vpn/TfsApiClient.py @@ -56,6 +56,7 @@ MAPPING_DRIVER = { 'DEVICEDRIVER_GNMI_NOKIA_SRLINUX' : 19, 'DEVICEDRIVER_OPENROADM' : 20, 'DEVICEDRIVER_RESTCONF_OPENCONFIG' : 21, + 'DEVICEDRIVER_CUSTOM_IPOWDM' : 22, } diff --git a/src/device/service/drivers/ietf_slice/TfsApiClient.py b/src/device/service/drivers/ietf_slice/TfsApiClient.py index 9cee9a03b..3a0261901 100644 --- a/src/device/service/drivers/ietf_slice/TfsApiClient.py +++ b/src/device/service/drivers/ietf_slice/TfsApiClient.py @@ -57,6 +57,7 @@ MAPPING_DRIVER = { 'DEVICEDRIVER_GNMI_NOKIA_SRLINUX' : 19, 'DEVICEDRIVER_OPENROADM' : 20, 'DEVICEDRIVER_RESTCONF_OPENCONFIG' : 21, + 'DEVICEDRIVER_CUSTOM_IPOWDM' : 22, } diff --git a/src/device/service/drivers/optical_tfs/TfsApiClient.py b/src/device/service/drivers/optical_tfs/TfsApiClient.py index 4cf97f73c..5e1ece17f 100644 --- a/src/device/service/drivers/optical_tfs/TfsApiClient.py +++ b/src/device/service/drivers/optical_tfs/TfsApiClient.py @@ -60,6 +60,7 @@ MAPPING_DRIVER = { 'DEVICEDRIVER_GNMI_NOKIA_SRLINUX' : 19, 'DEVICEDRIVER_OPENROADM' : 20, 'DEVICEDRIVER_RESTCONF_OPENCONFIG' : 21, + 'DEVICEDRIVER_CUSTOM_IPOWDM' : 22, } diff --git a/src/device/service/drivers/transport_api/TfsApiClient.py b/src/device/service/drivers/transport_api/TfsApiClient.py index e8962516e..76cacb986 100644 --- a/src/device/service/drivers/transport_api/TfsApiClient.py +++ b/src/device/service/drivers/transport_api/TfsApiClient.py @@ -47,6 +47,10 @@ MAPPING_DRIVER = { 'DEVICEDRIVER_SMARTNIC' : 16, 'DEVICEDRIVER_MORPHEUS' : 17, 'DEVICEDRIVER_RYU' : 18, + 'DEVICEDRIVER_GNMI_NOKIA_SRLINUX' : 19, + 'DEVICEDRIVER_OPENROADM' : 20, + 'DEVICEDRIVER_RESTCONF_OPENCONFIG' : 21, + 'DEVICEDRIVER_CUSTOM_IPOWDM' : 22, } LOGGER = logging.getLogger(__name__) diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java b/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java index 1dce54e76..f02a4c4b3 100644 --- a/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java +++ b/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java @@ -1752,6 +1752,8 @@ public class Serializer { return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_OPENROADM; case RESTCONF_OPENCONFIG: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG; + case CUSTOM_IPOWDM: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM; case UNDEFINED: default: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED; diff --git a/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java index 72c6cfffd..d28caa43d 100644 --- a/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java +++ b/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java @@ -2642,7 +2642,11 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.IETF_ACTN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN), Arguments.of( - DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); + DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED), + Arguments.of( + DeviceDriverEnum.RESTCONF_OPENCONFIG, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG), + Arguments.of( + DeviceDriverEnum.CUSTOM_IPOWDM, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM)); } @ParameterizedTest diff --git a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java index 27cde66fb..7ab7bca11 100644 --- a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java @@ -235,6 +235,10 @@ public final class ContextOuterClass { * DEVICEDRIVER_RESTCONF_OPENCONFIG = 21; */ DEVICEDRIVER_RESTCONF_OPENCONFIG(21), + /** + * DEVICEDRIVER_CUSTOM_IPOWDM = 22; + */ + DEVICEDRIVER_CUSTOM_IPOWDM(22), UNRECOGNIZED(-1); /** @@ -351,6 +355,11 @@ public final class ContextOuterClass { */ public static final int DEVICEDRIVER_RESTCONF_OPENCONFIG_VALUE = 21; + /** + * DEVICEDRIVER_CUSTOM_IPOWDM = 22; + */ + public static final int DEVICEDRIVER_CUSTOM_IPOWDM_VALUE = 22; + public final int getNumber() { if (this == UNRECOGNIZED) { throw new java.lang.IllegalArgumentException("Can't get the number of an unknown enum value."); @@ -418,6 +427,8 @@ public final class ContextOuterClass { return DEVICEDRIVER_OPENROADM; case 21: return DEVICEDRIVER_RESTCONF_OPENCONFIG; + case 22: + return DEVICEDRIVER_CUSTOM_IPOWDM; default: return null; } diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py index 23a19278c..c6d677ed8 100644 --- a/src/webui/service/device/routes.py +++ b/src/webui/service/device/routes.py @@ -155,6 +155,8 @@ def add(): device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_OPENROADM) if form.device_drivers_restconf_openconfig.data: device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG) + if form.device_drivers_custom_ipowdm.data: + device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM) device_obj.device_drivers.extend(device_drivers) # pylint: disable=no-member try: diff --git a/src/ztp/src/main/java/org/etsi/tfs/ztp/Serializer.java b/src/ztp/src/main/java/org/etsi/tfs/ztp/Serializer.java index 18677f88f..8c6ee944d 100644 --- a/src/ztp/src/main/java/org/etsi/tfs/ztp/Serializer.java +++ b/src/ztp/src/main/java/org/etsi/tfs/ztp/Serializer.java @@ -891,6 +891,8 @@ public class Serializer { return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_OPENROADM; case RESTCONF_OPENCONFIG: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG; + case CUSTOM_IPOWDM: + return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM; case UNDEFINED: default: return ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED; @@ -942,6 +944,8 @@ public class Serializer { return DeviceDriverEnum.OPENROADM; case DEVICEDRIVER_RESTCONF_OPENCONFIG: return DeviceDriverEnum.RESTCONF_OPENCONFIG; + case DEVICEDRIVER_CUSTOM_IPOWDM: + return DeviceDriverEnum.CUSTOM_IPOWDM; case DEVICEDRIVER_UNDEFINED: case UNRECOGNIZED: default: diff --git a/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java b/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java index eab65460c..b28d1b81a 100644 --- a/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java +++ b/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java @@ -1233,6 +1233,8 @@ class SerializerTest { DeviceDriverEnum.IETF_ACTN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN), Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); + Arguments.of( + DeviceDriverEnum.CUSTOM_IPOWDM, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM); } @ParameterizedTest diff --git a/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java b/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java index 27cde66fb..0a4254576 100644 --- a/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java @@ -235,6 +235,10 @@ public final class ContextOuterClass { * DEVICEDRIVER_RESTCONF_OPENCONFIG = 21; */ DEVICEDRIVER_RESTCONF_OPENCONFIG(21), + /** + * DEVICEDRIVER_CUSTOM_IPOWDM = 22; + */ + DEVICEDRIVER_CUSTOM_IPOWDM(22); UNRECOGNIZED(-1); /** @@ -351,6 +355,11 @@ public final class ContextOuterClass { */ public static final int DEVICEDRIVER_RESTCONF_OPENCONFIG_VALUE = 21; + /** + * DEVICEDRIVER_CUSTOM_IPOWDM =22; + */ + public static final int DEVICEDRIVER_CUSTOM_IPOWDM = 22; + public final int getNumber() { if (this == UNRECOGNIZED) { throw new java.lang.IllegalArgumentException("Can't get the number of an unknown enum value."); @@ -418,6 +427,8 @@ public final class ContextOuterClass { return DEVICEDRIVER_OPENROADM; case 21: return DEVICEDRIVER_RESTCONF_OPENCONFIG; + case 22: + return DEVICEDRIVER_CUSTOM_IPOWDM; default: return null; } -- GitLab From 968ece3863a0eef6b8db476390cad47ec9ec46a3 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Mon, 15 Jun 2026 13:41:20 +0000 Subject: [PATCH 3/9] Policy / ZTP components: - Add missing device driver identifier --- .../context/model/DeviceDriverEnum.java | 3 +- .../ztp/context/model/DeviceDriverEnum.java | 31 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/context/model/DeviceDriverEnum.java b/src/policy/src/main/java/org/etsi/tfs/policy/context/model/DeviceDriverEnum.java index 3d45ee265..3e1ae08e8 100644 --- a/src/policy/src/main/java/org/etsi/tfs/policy/context/model/DeviceDriverEnum.java +++ b/src/policy/src/main/java/org/etsi/tfs/policy/context/model/DeviceDriverEnum.java @@ -38,5 +38,6 @@ public enum DeviceDriverEnum { RYU, GNMI_NOKIA_SRLINUX, OPENROADM, - RESTCONF_OPENCONFIG + RESTCONF_OPENCONFIG, + CUSTOM_IPOWDM } diff --git a/src/ztp/src/main/java/org/etsi/tfs/ztp/context/model/DeviceDriverEnum.java b/src/ztp/src/main/java/org/etsi/tfs/ztp/context/model/DeviceDriverEnum.java index 9874e521d..5bb0fe048 100644 --- a/src/ztp/src/main/java/org/etsi/tfs/ztp/context/model/DeviceDriverEnum.java +++ b/src/ztp/src/main/java/org/etsi/tfs/ztp/context/model/DeviceDriverEnum.java @@ -1,18 +1,18 @@ /* -* Copyright 2022-2026 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. -* 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. -*/ + * Copyright 2022-2026 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. + * 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. + */ package org.etsi.tfs.ztp.context.model; @@ -38,5 +38,6 @@ public enum DeviceDriverEnum { RYU, GNMI_NOKIA_SRLINUX, OPENROADM, - RESTCONF_OPENCONFIG + RESTCONF_OPENCONFIG, + CUSTOM_IPOWDM } -- GitLab From 0a428963d46f9d1fc7e3e42b49718185e452426c Mon Sep 17 00:00:00 2001 From: armingol Date: Wed, 17 Jun 2026 09:59:05 +0000 Subject: [PATCH 4/9] fix: rename DEVICEDRIVER_CUSTOM_IPOWDM constant and update enum definition in ContextOuterClass --- .../generated-sources/grpc/context/ContextOuterClass.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java b/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java index 0a4254576..b94384590 100644 --- a/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java +++ b/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java @@ -238,7 +238,7 @@ public final class ContextOuterClass { /** * DEVICEDRIVER_CUSTOM_IPOWDM = 22; */ - DEVICEDRIVER_CUSTOM_IPOWDM(22); + DEVICEDRIVER_CUSTOM_IPOWDM(22), UNRECOGNIZED(-1); /** @@ -356,9 +356,9 @@ public final class ContextOuterClass { public static final int DEVICEDRIVER_RESTCONF_OPENCONFIG_VALUE = 21; /** - * DEVICEDRIVER_CUSTOM_IPOWDM =22; + * DEVICEDRIVER_CUSTOM_IPOWDM = 22; */ - public static final int DEVICEDRIVER_CUSTOM_IPOWDM = 22; + public static final int DEVICEDRIVER_CUSTOM_IPOWDM_VALUE = 22; public final int getNumber() { if (this == UNRECOGNIZED) { -- GitLab From 059da5064b3c5c22fdf8a7187ef26a85999ee421 Mon Sep 17 00:00:00 2001 From: armingol Date: Wed, 17 Jun 2026 10:03:27 +0000 Subject: [PATCH 5/9] feat: add support for CUSTOM_IPOWDM device driver in serializer and fix test case mapping --- src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java | 2 ++ src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java b/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java index f02a4c4b3..5639a2059 100644 --- a/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java +++ b/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java @@ -1805,6 +1805,8 @@ public class Serializer { return DeviceDriverEnum.OPENROADM; case DEVICEDRIVER_RESTCONF_OPENCONFIG: return DeviceDriverEnum.RESTCONF_OPENCONFIG; + case DEVICEDRIVER_CUSTOM_IPOWDM: + return DeviceDriverEnum.CUSTOM_IPOWDM; case DEVICEDRIVER_UNDEFINED: case UNRECOGNIZED: default: diff --git a/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java b/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java index b28d1b81a..f29b9eed4 100644 --- a/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java +++ b/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java @@ -1232,9 +1232,9 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.IETF_ACTN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN), Arguments.of( - DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); + DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM), Arguments.of( - DeviceDriverEnum.CUSTOM_IPOWDM, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM); + DeviceDriverEnum.CUSTOM_IPOWDM, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } @ParameterizedTest -- GitLab From 903034b91901912802b3701518078f3d54926686 Mon Sep 17 00:00:00 2001 From: armingol Date: Wed, 17 Jun 2026 10:18:45 +0000 Subject: [PATCH 6/9] style: reformat device driver arguments for better readability in SerializerTest --- .../src/test/java/org/etsi/tfs/policy/SerializerTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java index d28caa43d..af1f462c1 100644 --- a/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java +++ b/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java @@ -2644,9 +2644,11 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED), Arguments.of( - DeviceDriverEnum.RESTCONF_OPENCONFIG, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG), + DeviceDriverEnum.RESTCONF_OPENCONFIG, + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG), Arguments.of( - DeviceDriverEnum.CUSTOM_IPOWDM, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM)); + DeviceDriverEnum.CUSTOM_IPOWDM, + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM)); } @ParameterizedTest -- GitLab From faf40d05469311820f11f47f4932599f8749db07 Mon Sep 17 00:00:00 2001 From: armingol Date: Wed, 17 Jun 2026 10:27:08 +0000 Subject: [PATCH 7/9] refactor: rename CustomIPoWDMDriver to CustomIpowdmDriver to fix import path consistency --- src/device/service/drivers/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index 95d405e05..cc4fc178e 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -271,9 +271,9 @@ if LOAD_ALL_DEVICE_DRIVERS: } ])) if LOAD_ALL_DEVICE_DRIVERS: - from .custom_ipowdm.CustomIPoWDMDriver import CustomIPoWDMDriver # pylint: disable=wrong-import-position + from .custom_ipowdm.CustomIpowdmDriver import CustomIpowdmDriver # pylint: disable=wrong-import-position DRIVERS.append( - (CustomIPoWDMDriver, [ + (CustomIpowdmDriver, [ { FilterFieldEnum.DEVICE_TYPE: [ DeviceTypeEnum.PACKET_ROUTER -- GitLab From f982721034ad3d9c05ac77663fa38c8a55155ad3 Mon Sep 17 00:00:00 2001 From: armingol Date: Wed, 17 Jun 2026 10:58:20 +0000 Subject: [PATCH 8/9] test: fix incorrect device driver mappings in SerializerTest --- .../src/test/java/org/etsi/tfs/ztp/SerializerTest.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java b/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java index f29b9eed4..2a2ee9e54 100644 --- a/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java +++ b/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java @@ -1232,9 +1232,14 @@ class SerializerTest { Arguments.of( DeviceDriverEnum.IETF_ACTN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN), Arguments.of( - DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM), + DeviceDriverEnum.RESTCONF_OPENCONFIG, + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG), Arguments.of( - DeviceDriverEnum.CUSTOM_IPOWDM, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); + DeviceDriverEnum.CUSTOM_IPOWDM, + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM), + Arguments.of( + DeviceDriverEnum.UNDEFINED, + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } @ParameterizedTest -- GitLab From e165fa94e5775e021110d284aa09fa9d8b69a589 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 17 Jun 2026 16:15:23 +0000 Subject: [PATCH 9/9] pre-merge code cleanup --- src/device/service/drivers/__init__.py | 2 +- src/device/service/drivers/custom_ipowdm/Tools.py | 2 +- src/nbi/service/ipowdm/Resources.py | 2 +- src/nbi/service/ipowdm/__init__.py | 2 +- .../src/test/java/org/etsi/tfs/policy/SerializerTest.java | 6 +++--- src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java | 3 ++- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index cc4fc178e..7c4338162 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -280,4 +280,4 @@ if LOAD_ALL_DEVICE_DRIVERS: ], FilterFieldEnum.DRIVER : DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM, } - ])) \ No newline at end of file + ])) diff --git a/src/device/service/drivers/custom_ipowdm/Tools.py b/src/device/service/drivers/custom_ipowdm/Tools.py index 89f7724f9..8cfef9c32 100644 --- a/src/device/service/drivers/custom_ipowdm/Tools.py +++ b/src/device/service/drivers/custom_ipowdm/Tools.py @@ -1,4 +1,4 @@ -# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# Copyright 2022-2026 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/ipowdm/Resources.py b/src/nbi/service/ipowdm/Resources.py index dbe1dd3cc..945c6ab6b 100644 --- a/src/nbi/service/ipowdm/Resources.py +++ b/src/nbi/service/ipowdm/Resources.py @@ -1,4 +1,4 @@ -# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# Copyright 2022-2026 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/ipowdm/__init__.py b/src/nbi/service/ipowdm/__init__.py index 39cadfb14..3bec4e132 100644 --- a/src/nbi/service/ipowdm/__init__.py +++ b/src/nbi/service/ipowdm/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# Copyright 2022-2026 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/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java index af1f462c1..8ae266365 100644 --- a/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java +++ b/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java @@ -2641,14 +2641,14 @@ class SerializerTest { ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_OPTICAL_TFS), Arguments.of( DeviceDriverEnum.IETF_ACTN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN), - Arguments.of( - DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED), Arguments.of( DeviceDriverEnum.RESTCONF_OPENCONFIG, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG), Arguments.of( DeviceDriverEnum.CUSTOM_IPOWDM, - ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM)); + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM), + Arguments.of( + DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED)); } @ParameterizedTest diff --git a/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java b/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java index 2a2ee9e54..e93114406 100644 --- a/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java +++ b/src/ztp/src/test/java/org/etsi/tfs/ztp/SerializerTest.java @@ -1230,7 +1230,8 @@ class SerializerTest { DeviceDriverEnum.OPTICAL_TFS, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_OPTICAL_TFS), Arguments.of( - DeviceDriverEnum.IETF_ACTN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN), + DeviceDriverEnum.IETF_ACTN, + ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN), Arguments.of( DeviceDriverEnum.RESTCONF_OPENCONFIG, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG), -- GitLab