diff --git a/proto/context.proto b/proto/context.proto
index 3550fb1808d7c0e2c8aab78d369ab2b0e29d5e09..7d6ba1bc14bf66b8018fec421af49c54e124494b 100644
--- a/proto/context.proto
+++ b/proto/context.proto
@@ -260,6 +260,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/common/type_checkers/Assertions.py b/src/common/type_checkers/Assertions.py
index 2fa279de593e4edc09d93627219e24c49b7d5fd0..51ca9f7aadf65f9e93715e0f48ce9ff7151a16a5 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 5c228c87ebe2ce5db8140d59716bc960c0184a81..d029af443eabf315819dd13f479dee8c158c02a0 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 85ea4bef06a1e3facaaa583d4b9be63efac3da34..7c4338162ef59c8958c4127fea5d75ce66c07b1c 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,
+ }
+ ]))
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 0000000000000000000000000000000000000000..51ac1c483436d8daafed474b4c239f980ae9d496
--- /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 0000000000000000000000000000000000000000..f1f583382fc67d3dd875e6dbcfe84dd1087fee16
--- /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 0000000000000000000000000000000000000000..52584a3454e1d7134e5f79c3d765a130041f664e
--- /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 0000000000000000000000000000000000000000..8cfef9c323d1fcd717e47fd0e0ea7d6f126b8f17
--- /dev/null
+++ b/src/device/service/drivers/custom_ipowdm/Tools.py
@@ -0,0 +1,134 @@
+# 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 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 0000000000000000000000000000000000000000..b53987a4eae1aed245eba5c7ddd8cd10e35919c2
--- /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.
diff --git a/src/device/service/drivers/ietf_l2vpn/TfsApiClient.py b/src/device/service/drivers/ietf_l2vpn/TfsApiClient.py
index aeb4132bd0229c8e2129908a952425d74f95ce33..88ec07a09bc9427b18b2a913914b03b1028e4e4b 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 1ada5ddef3d92afe7de7fbd0600bd3fbf23eb371..947b3ca5c3c41aa4f928044fe834f76fd778f136 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 9cee9a03bd717c743b0984714d6ce5278db2a4ee..3a0261901c2fd176d09f44e7540ddb1b273f9f1a 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 4cf97f73c353690e08ede8f0fe215bdc7c0f20f4..5e1ece17f9645492a38463cada82dc9c38f043cf 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 e8962516e787b9f1382beb651f27115bb66119ec..76cacb98649d41c8e3c0dff5df50e5aa556abbc4 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/nbi/service/ipowdm/Resources.py b/src/nbi/service/ipowdm/Resources.py
index dbe1dd3ccb5ca60d0825284492a4a5b044eb382e..945c6ab6b7b7a31b17b2dc5cd6efe03efc3338a4 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 39cadfb1464e3b02fe0075f4230ae739610aa05d..3bec4e132f7a9a398e74091629256428dc5075c8 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/main/java/org/etsi/tfs/policy/Serializer.java b/src/policy/src/main/java/org/etsi/tfs/policy/Serializer.java
index 1dce54e76307f0819e88e7fc87fd3d0eecf2650f..5639a2059235791127826a114b5f47842ce78e08 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;
@@ -1803,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/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 3d45ee265341211b7c7f56158f731bc7263188e4..3e1ae08e888d39fbe964a58b0fa711e00d5e2211 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/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java b/src/policy/src/test/java/org/etsi/tfs/policy/SerializerTest.java
index 72c6cfffdd134458c39c1f9d4cfbda82c03ce831..8ae266365013e2950efc5fbf01485d6346ba2661 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,6 +2641,12 @@ class SerializerTest {
ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_OPTICAL_TFS),
Arguments.of(
DeviceDriverEnum.IETF_ACTN, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN),
+ Arguments.of(
+ DeviceDriverEnum.RESTCONF_OPENCONFIG,
+ ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG),
+ Arguments.of(
+ DeviceDriverEnum.CUSTOM_IPOWDM,
+ ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM),
Arguments.of(
DeviceDriverEnum.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED));
}
diff --git a/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java b/src/policy/target/generated-sources/grpc/context/ContextOuterClass.java
index 27cde66fb4d01c41c0ae39bba4f4da897b2cd0c5..7ab7bca1180dfffef6fcaf89412251ed7612caf5 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 e1cfb000a97e46e6ccda2ea4eb5c16dac2b8ea08..3eac49150e2e9ecf91b9a3df26924b204155baeb 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 18677f88fb4ba7cf4692746d7a58d99fcf33a0d5..8c6ee944d066987ce19de4038f399b5ad7d54b90 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/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 9874e521d199735ef80d24b7efa1b90ff0d1bea9..5bb0fe04874af8a9e0c21009fdeee6e2e1fcaa2c 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
}
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 eab65460c2333bdbb595db1b4656753b59a7ca8e..e93114406b2ccf5c2d117a01614a373d4f7f1be2 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,9 +1230,17 @@ 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.UNDEFINED, ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED));
+ DeviceDriverEnum.RESTCONF_OPENCONFIG,
+ ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG),
+ Arguments.of(
+ DeviceDriverEnum.CUSTOM_IPOWDM,
+ ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_CUSTOM_IPOWDM),
+ Arguments.of(
+ DeviceDriverEnum.UNDEFINED,
+ ContextOuterClass.DeviceDriverEnum.DEVICEDRIVER_UNDEFINED));
}
@ParameterizedTest
diff --git a/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java b/src/ztp/target/generated-sources/grpc/context/ContextOuterClass.java
index 27cde66fb4d01c41c0ae39bba4f4da897b2cd0c5..b9438459054b3cc769f705b92464a54049951487 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_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;
}