diff --git a/proto/context.proto b/proto/context.proto index 4d61572df1db6c95b64e9ce1cbfdd9e0db111078..c527078d67c906b8a725b3e1563677f48dd9fb37 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -223,6 +223,7 @@ enum DeviceDriverEnum { DEVICEDRIVER_IETF_ACTN = 10; DEVICEDRIVER_OC = 11; DEVICEDRIVER_QKD = 12; + DEVICEDRIVER_IETF_L3VPN = 13; } enum DeviceOperationalStatusEnum { diff --git a/proto/kpi_sample_types.proto b/proto/kpi_sample_types.proto index 0a9800d9e5839205e1e45f84e4c8bdafbe93f32f..d4efc084e5f1ea2376e71ef6a15bc9b972c5ac1d 100644 --- a/proto/kpi_sample_types.proto +++ b/proto/kpi_sample_types.proto @@ -39,4 +39,14 @@ enum KpiSampleType { KPISAMPLETYPE_L3_SECURITY_STATUS_CRYPTO = 605; KPISAMPLETYPE_SERVICE_LATENCY_MS = 701; + +// output KPIs + KPISAMPLETYPE_PACKETS_TRANSMITTED_AGG_OUTPUT = 1101; + KPISAMPLETYPE_PACKETS_RECEIVED_AGG_OUTPUT = 1102; + KPISAMPLETYPE_PACKETS_DROPPED_AGG_OUTPUT = 1103; + KPISAMPLETYPE_BYTES_TRANSMITTED_AGG_OUTPUT = 1201; + KPISAMPLETYPE_BYTES_RECEIVED_AGG_OUTPUT = 1202; + KPISAMPLETYPE_BYTES_DROPPED_AGG_OUTPUT = 1203; + + KPISAMPLETYPE_SERVICE_LATENCY_MS_AGG_OUTPUT = 1701; } diff --git a/scripts/run_tests_locally-analytics-backend.sh b/scripts/run_tests_locally-analytics-backend.sh index 1c3386c62084bb42d6ffa2e1349b6f4286820a52..700155a42714bd05069c7c62db9ada09b4125355 100755 --- a/scripts/run_tests_locally-analytics-backend.sh +++ b/scripts/run_tests_locally-analytics-backend.sh @@ -18,8 +18,11 @@ PROJECTDIR=`pwd` cd $PROJECTDIR/src RCFILE=$PROJECTDIR/coverage/.coveragerc + export KFK_SERVER_ADDRESS='127.0.0.1:9092' + CRDB_SQL_ADDRESS=$(kubectl get service cockroachdb-public --namespace crdb -o jsonpath='{.spec.clusterIP}') export CRDB_URI="cockroachdb://tfs:tfs123@${CRDB_SQL_ADDRESS}:26257/tfs_analytics?sslmode=require" -python3 -m pytest --log-level=DEBUG --log-cli-level=DEBUG --verbose \ + +python3 -m pytest --log-level=DEBUG --log-cli-level=INFO --verbose \ analytics/backend/tests/test_backend.py diff --git a/scripts/run_tests_locally-analytics-frontend.sh b/scripts/run_tests_locally-analytics-frontend.sh index 6e945406f0ff7b2670a35d5315d0ef428f701988..2c18296cf86ae4a00904fb684e2de5c56da9a2ea 100755 --- a/scripts/run_tests_locally-analytics-frontend.sh +++ b/scripts/run_tests_locally-analytics-frontend.sh @@ -18,8 +18,10 @@ PROJECTDIR=`pwd` cd $PROJECTDIR/src RCFILE=$PROJECTDIR/coverage/.coveragerc + export KFK_SERVER_ADDRESS='127.0.0.1:9092' CRDB_SQL_ADDRESS=$(kubectl get service cockroachdb-public --namespace crdb -o jsonpath='{.spec.clusterIP}') export CRDB_URI="cockroachdb://tfs:tfs123@${CRDB_SQL_ADDRESS}:26257/tfs_analytics?sslmode=require" -python3 -m pytest --log-level=DEBUG --log-cli-level=DEBUG --verbose \ + +python3 -m pytest --log-level=DEBUG --log-cli-level=INFO --verbose \ analytics/frontend/tests/test_frontend.py diff --git a/scripts/run_tests_locally-nbi-ietf-slice.sh b/scripts/run_tests_locally-nbi-ietf-slice.sh new file mode 100755 index 0000000000000000000000000000000000000000..bf53f18b9a37f9248b072ae2c699b5874fa2c869 --- /dev/null +++ b/scripts/run_tests_locally-nbi-ietf-slice.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +PROJECTDIR=`pwd` + +cd $PROJECTDIR/src +RCFILE=$PROJECTDIR/coverage/.coveragerc + +# Run unitary tests and analyze coverage of code at same time +# helpful pytest flags: --log-level=INFO -o log_cli=true --verbose --maxfail=1 --durations=0 +coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ + nbi/tests/test_slice_2.py diff --git a/scripts/run_tests_locally-telemetry-backend.sh b/scripts/run_tests_locally-telemetry-backend.sh index 3ad4a2d0e05dbb11573eb146b4f0a4959894ace0..f648a62520f2f7b23f30edb19bf54735f5d13e12 100755 --- a/scripts/run_tests_locally-telemetry-backend.sh +++ b/scripts/run_tests_locally-telemetry-backend.sh @@ -18,15 +18,12 @@ PROJECTDIR=`pwd` cd $PROJECTDIR/src # RCFILE=$PROJECTDIR/coverage/.coveragerc -# coverage run --rcfile=$RCFILE --append -m pytest --log-level=INFO --verbose \ -# kpi_manager/tests/test_unitary.py -# python3 kpi_manager/tests/test_unitary.py export KFK_SERVER_ADDRESS='127.0.0.1:9092' CRDB_SQL_ADDRESS=$(kubectl get service cockroachdb-public --namespace crdb -o jsonpath='{.spec.clusterIP}') export CRDB_URI="cockroachdb://tfs:tfs123@${CRDB_SQL_ADDRESS}:26257/tfs_telemetry?sslmode=require" RCFILE=$PROJECTDIR/coverage/.coveragerc -python3 -m pytest --log-level=INFO --log-cli-level=debug --verbose \ - telemetry/backend/tests/test_TelemetryBackend.py +python3 -m pytest --log-level=debug --log-cli-level=debug --verbose \ + telemetry/backend/tests/test_backend.py diff --git a/scripts/run_tests_locally-telemetry-emulated.sh b/scripts/run_tests_locally-telemetry-emulated.sh new file mode 100755 index 0000000000000000000000000000000000000000..879b878c7281a17dcc89a36ff146939151540ec4 --- /dev/null +++ b/scripts/run_tests_locally-telemetry-emulated.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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. + + +PROJECTDIR=`pwd` + +cd $PROJECTDIR/src +# RCFILE=$PROJECTDIR/coverage/.coveragerc + +# export KFK_SERVER_ADDRESS='127.0.0.1:9092' +# CRDB_SQL_ADDRESS=$(kubectl get service cockroachdb-public --namespace crdb -o jsonpath='{.spec.clusterIP}') +# export CRDB_URI="cockroachdb://tfs:tfs123@${CRDB_SQL_ADDRESS}:26257/tfs_telemetry?sslmode=require" +RCFILE=$PROJECTDIR/coverage/.coveragerc + + +python3 -m pytest --log-level=debug --log-cli-level=info --verbose \ + telemetry/backend/tests/test_emulated.py diff --git a/src/analytics/backend/service/AnalyticsBackendService.py b/src/analytics/backend/service/AnalyticsBackendService.py index f3a58feaab8667b266052803dddd1641b8a690f3..1abdd62c0f7162fe7a50f1c08e698f71c5fc93ad 100755 --- a/src/analytics/backend/service/AnalyticsBackendService.py +++ b/src/analytics/backend/service/AnalyticsBackendService.py @@ -16,109 +16,164 @@ import time import json import logging import threading + from common.tools.service.GenericGrpcService import GenericGrpcService from common.tools.kafka.Variables import KafkaConfig, KafkaTopic -from confluent_kafka import Consumer as KafkaConsumer -from confluent_kafka import KafkaError +from confluent_kafka import Consumer +from confluent_kafka import KafkaError, KafkaException from common.Constants import ServiceNameEnum from common.Settings import get_service_port_grpc -from threading import Thread, Event -from .DaskStreaming import DaskStreamer +from analytics.backend.service.Streamer import DaskStreamer +from analytics.backend.service.AnalyzerHelper import AnalyzerHelper + LOGGER = logging.getLogger(__name__) class AnalyticsBackendService(GenericGrpcService): """ - Class listens for ... + AnalyticsBackendService class is responsible for handling the requests from the AnalyticsFrontendService. + It listens to the Kafka topic for the requests to start and stop the Streamer accordingly. + It also initializes the Kafka producer and Dask cluster for the streamer. """ - def __init__(self, cls_name : str = __name__) -> None: + def __init__(self, cls_name : str = __name__, n_workers=1, threads_per_worker=1 + ) -> None: LOGGER.info('Init AnalyticsBackendService') port = get_service_port_grpc(ServiceNameEnum.ANALYTICSBACKEND) super().__init__(port, cls_name=cls_name) - self.running_threads = {} # To keep track of all running analyzers - self.kafka_consumer = KafkaConsumer({'bootstrap.servers' : KafkaConfig.get_kafka_address(), - 'group.id' : 'analytics-frontend', - 'auto.offset.reset' : 'latest'}) + self.active_streamers = {} + self.central_producer = AnalyzerHelper.initialize_kafka_producer() # Multi-threaded producer + self.cluster = AnalyzerHelper.initialize_dask_cluster( + n_workers, threads_per_worker) # Local cluster + self.request_consumer = Consumer({ + 'bootstrap.servers' : KafkaConfig.get_kafka_address(), + 'group.id' : 'analytics-backend', + 'auto.offset.reset' : 'latest', + }) + def install_servicers(self): - threading.Thread(target=self.RequestListener, args=()).start() + threading.Thread( + target=self.RequestListener, + args=() + ).start() def RequestListener(self): """ listener for requests on Kafka topic. """ LOGGER.info("Request Listener is initiated ...") - # print ("Request Listener is initiated ...") - consumer = self.kafka_consumer + consumer = self.request_consumer consumer.subscribe([KafkaTopic.ANALYTICS_REQUEST.value]) while True: - receive_msg = consumer.poll(2.0) - if receive_msg is None: + message = consumer.poll(2.0) + if message is None: continue - elif receive_msg.error(): - if receive_msg.error().code() == KafkaError._PARTITION_EOF: - continue - else: - LOGGER.error("Consumer error: {:}".format(receive_msg.error())) - # print ("Consumer error: {:}".format(receive_msg.error())) - break + elif message.error(): + if message.error().code() == KafkaError._PARTITION_EOF: + LOGGER.warning(f"Consumer reached end of topic {message.topic()}/{message.partition()}") + break + elif message.error().code() == KafkaError.UNKNOWN_TOPIC_OR_PART: + LOGGER.error(f"Subscribed topic {message.topic()} does not exist. May be topic does not have any messages.") + continue + elif message.error(): + raise KafkaException(message.error()) try: - analyzer = json.loads(receive_msg.value().decode('utf-8')) - analyzer_uuid = receive_msg.key().decode('utf-8') - LOGGER.debug('Recevied Analyzer: {:} - {:}'.format(analyzer_uuid, analyzer)) - # print ('Recevied Analyzer: {:} - {:}'.format(analyzer_uuid, analyzer)) + analyzer = json.loads(message.value().decode('utf-8')) + analyzer_uuid = message.key().decode('utf-8') + LOGGER.info('Recevied Analyzer: {:} - {:}'.format(analyzer_uuid, analyzer)) if analyzer["algo_name"] is None and analyzer["oper_mode"] is None: - self.StopDaskListener(analyzer_uuid) + if self.StopStreamer(analyzer_uuid): + LOGGER.info("Dask Streamer stopped.") + else: + LOGGER.warning("Failed to stop Dask Streamer. May be already terminated...") else: - self.StartDaskListener(analyzer_uuid, analyzer) + if self.StartStreamer(analyzer_uuid, analyzer): + LOGGER.info("Dask Streamer started.") + else: + LOGGER.warning("Failed to start Dask Streamer.") except Exception as e: LOGGER.warning("Unable to consume message from topic: {:}. ERROR: {:}".format(KafkaTopic.ANALYTICS_REQUEST.value, e)) - # print ("Unable to consume message from topic: {:}. ERROR: {:}".format(KafkaTopic.ANALYTICS_REQUEST.value, e)) - - def StartDaskListener(self, analyzer_uuid, analyzer): - kpi_list = analyzer[ 'input_kpis' ] - thresholds = analyzer[ 'thresholds' ] - window_size = analyzer[ 'window_size' ] - window_slider = analyzer[ 'window_slider'] - - LOGGER.debug ("Received parameters: {:} - {:} - {:} - {:}".format( - kpi_list, thresholds, window_size, window_slider)) - # print ("Received parameters: {:} - {:} - {:} - {:}".format( - # kpi_list, thresholds, window_size, window_slider)) + + + def StartStreamer(self, analyzer_uuid : str, analyzer : dict): + """ + Start the DaskStreamer with the given parameters. + """ + if analyzer_uuid in self.active_streamers: + LOGGER.warning("Dask Streamer already running with the given analyzer_uuid: {:}".format(analyzer_uuid)) + return False try: - stop_event = Event() - thread = Thread( - target=DaskStreamer, - # args=(analyzer_uuid, kpi_list, oper_list, thresholds, stop_event), - args=(analyzer['output_kpis'][0] , kpi_list, thresholds, stop_event), - kwargs={ - "window_size" : window_size, - } + streamer = DaskStreamer( + key = analyzer_uuid, + input_kpis = analyzer['input_kpis' ], + output_kpis = analyzer['output_kpis' ], + thresholds = analyzer['thresholds' ], + batch_size = analyzer['batch_size_min' ], + batch_duration = analyzer['batch_duration_min'], + window_size = analyzer['window_size' ], + cluster_instance = self.cluster, + producer_instance = self.central_producer, ) - thread.start() - self.running_threads[analyzer_uuid] = (thread, stop_event) - # print ("Initiated Analyzer backend: {:}".format(analyzer_uuid)) - LOGGER.info("Initiated Analyzer backend: {:}".format(analyzer_uuid)) + streamer.start() + LOGGER.info(f"Streamer started with analyzer Id: {analyzer_uuid}") + + # Stop the streamer after the given duration + duration = analyzer['duration'] + if duration > 0: + def stop_after_duration(): + time.sleep(duration) + LOGGER.warning(f"Execution duration ({duration}) completed of Analyzer: {analyzer_uuid}") + if not self.StopStreamer(analyzer_uuid): + LOGGER.warning("Failed to stop Dask Streamer. Streamer may be already terminated.") + + duration_thread = threading.Thread( + target=stop_after_duration, daemon=True, name=f"stop_after_duration_{analyzer_uuid}" + ) + duration_thread.start() + + self.active_streamers[analyzer_uuid] = streamer return True except Exception as e: - # print ("Failed to initiate Analyzer backend: {:}".format(e)) - LOGGER.error("Failed to initiate Analyzer backend: {:}".format(e)) + LOGGER.error("Failed to start Dask Streamer. ERROR: {:}".format(e)) return False - def StopDaskListener(self, analyzer_uuid): - if analyzer_uuid in self.running_threads: - try: - thread, stop_event = self.running_threads[analyzer_uuid] - stop_event.set() - thread.join() - del self.running_threads[analyzer_uuid] - # print ("Terminating backend (by TerminateBackend): Analyzer Id: {:}".format(analyzer_uuid)) - LOGGER.info("Terminating backend (by TerminateBackend): Analyzer Id: {:}".format(analyzer_uuid)) + def StopStreamer(self, analyzer_uuid : str): + """ + Stop the DaskStreamer with the given analyzer_uuid. + """ + try: + if analyzer_uuid not in self.active_streamers: + LOGGER.warning("Dask Streamer not found with the given analyzer_uuid: {:}".format(analyzer_uuid)) return True - except Exception as e: - LOGGER.error("Failed to terminate. Analyzer Id: {:} - ERROR: {:}".format(analyzer_uuid, e)) - return False - else: - # print ("Analyzer not found in active collectors. Analyzer Id: {:}".format(analyzer_uuid)) - LOGGER.warning("Analyzer not found in active collectors: Analyzer Id: {:}".format(analyzer_uuid)) + LOGGER.info(f"Terminating streamer with Analyzer Id: {analyzer_uuid}") + streamer = self.active_streamers[analyzer_uuid] + streamer.stop() + streamer.join() + del self.active_streamers[analyzer_uuid] + LOGGER.info(f"Streamer with analyzer_uuid '{analyzer_uuid}' has been trerminated sucessfully.") + return True + except: + LOGGER.exception("Failed to stop Dask Streamer.") + return False + + def close(self): + """ + Close the producer and cluster cleanly. + """ + if self.central_producer: + try: + self.central_producer.flush() + LOGGER.info("Kafka producer flushed and closed.") + except: + LOGGER.exception("Error closing Kafka producer") + if self.cluster: + try: + self.cluster.close() + LOGGER.info("Dask cluster closed.") + except: + LOGGER.exception("Error closing Dask cluster") + + def stop(self): + self.close() + return super().stop() diff --git a/src/analytics/backend/service/AnalyzerHandlers.py b/src/analytics/backend/service/AnalyzerHandlers.py new file mode 100644 index 0000000000000000000000000000000000000000..256530ba78329f09327b551f0238f4dd8a1258b5 --- /dev/null +++ b/src/analytics/backend/service/AnalyzerHandlers.py @@ -0,0 +1,136 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from enum import Enum +import pandas as pd + +logger = logging.getLogger(__name__) + + +class Handlers(Enum): + AGGREGATION_HANDLER = "AggregationHandler" + UNSUPPORTED_HANDLER = "UnsupportedHandler" + + @classmethod + def is_valid_handler(cls, handler_name): + return handler_name in cls._value2member_map_ + +# This method is top-level and should not be part of the class due to serialization issues. +def threshold_handler(key, aggregated_df, thresholds): + """ + Apply thresholds (TH-Fall and TH-Raise) based on the thresholds dictionary + on the aggregated DataFrame. + + Args: + key (str): Key for the aggregated DataFrame. + aggregated_df (pd.DataFrame): DataFrame with aggregated metrics. + thresholds (dict): Thresholds dictionary with keys in the format '<metricName>' and values as (fail_th, raise_th). + + Returns: + pd.DataFrame: DataFrame with additional threshold columns. + """ + for metric_name, threshold_values in thresholds.items(): + # Ensure the metric column exists in the DataFrame + if metric_name not in aggregated_df.columns: + logger.warning(f"Metric '{metric_name}' does not exist in the DataFrame for key: {key}. Skipping threshold application.") + continue + + # Ensure the threshold values are valid (check for tuple specifically) + if isinstance(threshold_values, list) and len(threshold_values) == 2: + fail_th, raise_th = threshold_values + + # Add threshold columns with updated naming + aggregated_df[f"{metric_name}_TH_RAISE"] = aggregated_df[metric_name] > raise_th + aggregated_df[f"{metric_name}_TH_FALL"] = aggregated_df[metric_name] < fail_th + else: + logger.warning(f"Threshold values for '{metric_name}' ({threshold_values}) are not a list of length 2. Skipping threshold application.") + return aggregated_df + +def aggregation_handler( + batch_type_name, key, batch, input_kpi_list, output_kpi_list, thresholds + ): + """ + Process a batch of data and calculate aggregated values for each input KPI + and maps them to the output KPIs. """ + + logger.info(f"({batch_type_name}) Processing batch for key: {key}") + if not batch: + logger.info("Empty batch received. Skipping processing.") + return [] + else: + logger.info(f" >>>>> Processing {len(batch)} records for key: {key}") + + # Convert data into a DataFrame + df = pd.DataFrame(batch) + + # Filter the DataFrame to retain rows where kpi_id is in the input list (subscribed endpoints only) + df = df[df['kpi_id'].isin(input_kpi_list)].copy() + + if df.empty: + logger.warning(f"No data available for KPIs: {input_kpi_list}. Skipping processing.") + return [] + + # Define all possible aggregation methods + aggregation_methods = { + "min" : ('kpi_value', 'min'), + "max" : ('kpi_value', 'max'), + "avg" : ('kpi_value', 'mean'), + "first" : ('kpi_value', lambda x: x.iloc[0]), + "last" : ('kpi_value', lambda x: x.iloc[-1]), + "variance": ('kpi_value', 'var'), + "count" : ('kpi_value', 'count'), + "range" : ('kpi_value', lambda x: x.max() - x.min()), + "sum" : ('kpi_value', 'sum'), + } + + results = [] + + # Process each KPI-specific task parameter + for kpi_index, kpi_id in enumerate(input_kpi_list): + + # logger.info(f"1.Processing KPI: {kpi_id}") + kpi_task_parameters = thresholds["task_parameter"][kpi_index] + + # Get valid task parameters for this KPI + valid_task_parameters = [ + method for method in kpi_task_parameters.keys() + if method in aggregation_methods + ] + + # Select the aggregation methods based on valid task parameters + selected_methods = {method: aggregation_methods[method] for method in valid_task_parameters} + + # logger.info(f"2. Processing KPI: {kpi_id} with task parameters: {kpi_task_parameters}") + kpi_df = df[df['kpi_id'] == kpi_id] + + # Check if kpi_df is not empty before applying the aggregation methods + if not kpi_df.empty: + agg_df = kpi_df.groupby('kpi_id').agg(**selected_methods).reset_index() + + # logger.info(f"3. Aggregated DataFrame for KPI: {kpi_id}: {agg_df}") + + agg_df['kpi_id'] = output_kpi_list[kpi_index] + + # logger.info(f"4. Applying thresholds for df: {agg_df['kpi_id']}") + record = threshold_handler(key, agg_df, kpi_task_parameters) + + results.extend(record.to_dict(orient='records')) + else: + logger.warning(f"No data available for KPIs: {kpi_id}. Skipping aggregation.") + continue + if results: + return results + else: + return [] diff --git a/src/analytics/backend/service/AnalyzerHelper.py b/src/analytics/backend/service/AnalyzerHelper.py new file mode 100644 index 0000000000000000000000000000000000000000..15a45aee68341c599905983efd79737a9d4929ab --- /dev/null +++ b/src/analytics/backend/service/AnalyzerHelper.py @@ -0,0 +1,67 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 dask.distributed import Client, LocalCluster +from common.tools.kafka.Variables import KafkaConfig, KafkaTopic +from confluent_kafka import Consumer, Producer + +import logging +logger = logging.getLogger(__name__) + + +class AnalyzerHelper: + def __init__(self): + pass + + @staticmethod + def initialize_dask_client(cluster_instance): + """Initialize a local Dask client.""" + if cluster_instance is None: + logger.error("Dask Cluster is not initialized. Exiting.") + return None + client = Client(cluster_instance) + logger.info(f"Dask Client Initialized: {client}") + return client + + @staticmethod + def initialize_dask_cluster(n_workers=1, threads_per_worker=2): + """Initialize a local Dask cluster""" + cluster = LocalCluster(n_workers=n_workers, threads_per_worker=threads_per_worker) + logger.info(f"Dask Cluster Initialized: {cluster}") + return cluster + + @staticmethod + def initialize_kafka_consumer(): # TODO: update to receive topic and group_id as parameters + """Initialize the Kafka consumer.""" + consumer_conf = { + 'bootstrap.servers': KafkaConfig.get_kafka_address(), + 'group.id': 'analytics-backend', + 'auto.offset.reset': 'latest' + } + consumer = Consumer(consumer_conf) + consumer.subscribe([KafkaTopic.VALUE.value]) + return consumer + + @staticmethod + def initialize_kafka_producer(): + """Initialize the Kafka producer.""" + return Producer({'bootstrap.servers': KafkaConfig.get_kafka_address()}) + + @staticmethod + def delivery_report(err, msg): + if err is not None: + logger.error(f"Message delivery failed: {err}") + else: + logger.debug(f"Message delivered to {msg.topic()} [{msg.partition()}] at offset {msg.offset()}") diff --git a/src/analytics/backend/service/DaskStreaming.py b/src/analytics/backend/service/DaskStreaming.py deleted file mode 100644 index 79dee7ef972a8b5546e3b38289c4fdb5b4bcc0d1..0000000000000000000000000000000000000000 --- a/src/analytics/backend/service/DaskStreaming.py +++ /dev/null @@ -1,268 +0,0 @@ -# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# 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 -import time -import json -from confluent_kafka import Consumer, Producer, KafkaException, KafkaError -import pandas as pd -from dask.distributed import Client, LocalCluster -from common.tools.kafka.Variables import KafkaConfig, KafkaTopic - -logging.basicConfig(level=logging.INFO) -LOGGER = logging.getLogger(__name__) - -def SettingKafkaConsumerParams(): - return {'bootstrap.servers' : KafkaConfig.get_kafka_address(), - 'group.id' : 'analytics-backend', - 'auto.offset.reset' : 'latest'} - -def GetAggregationMappings(thresholds): - agg_dict = {} - for threshold_key in thresholds.keys(): - parts = threshold_key.split('_', 1) - if len(parts) != 2: - LOGGER.warning(f"Threshold key '{threshold_key}' does not follow the '<aggregation>_<metricName>' format. Skipping.") - continue - aggregation, metric_name = parts - # Ensure that the aggregation function is valid in pandas - if aggregation not in ['mean', 'min', 'max', 'first', 'last', 'std']: - LOGGER.warning(f"Unsupported aggregation '{aggregation}' in threshold key '{threshold_key}'. Skipping.") - continue - agg_dict[threshold_key] = ('kpi_value', aggregation) - return agg_dict - - -def ApplyThresholds(aggregated_df, thresholds): - """ - Apply thresholds (TH-Fall and TH-Raise) based on the thresholds dictionary - on the aggregated DataFrame. - Args: aggregated_df (pd.DataFrame): DataFrame with aggregated metrics. - thresholds (dict): Thresholds dictionary with keys in the format '<aggregation>_<metricName>'. - Returns: pd.DataFrame: DataFrame with additional threshold columns. - """ - for threshold_key, threshold_values in thresholds.items(): - if threshold_key not in aggregated_df.columns: - - LOGGER.warning(f"Threshold key '{threshold_key}' does not correspond to any aggregation result. Skipping threshold application.") - continue - if isinstance(threshold_values, (list, tuple)) and len(threshold_values) == 2: - fail_th, raise_th = threshold_values - aggregated_df["THRESHOLD_FALL"] = aggregated_df[threshold_key] < fail_th - aggregated_df["THRESHOLD_RAISE"] = aggregated_df[threshold_key] > raise_th - aggregated_df["value"] = aggregated_df[threshold_key] - else: - LOGGER.warning(f"Threshold values for '{threshold_key}' are not a list or tuple of length 2. Skipping threshold application.") - return aggregated_df - -def initialize_dask_client(): - """ - Initialize a local Dask cluster and client. - """ - cluster = LocalCluster(n_workers=2, threads_per_worker=2) - client = Client(cluster) - LOGGER.info(f"Dask Client Initialized: {client}") - return client, cluster - -def initialize_kafka_producer(): - return Producer({'bootstrap.servers': KafkaConfig.get_kafka_address()}) - -def delivery_report(err, msg): - if err is not None: - LOGGER.error(f"Message delivery failed: {err}") - else: - LOGGER.info(f"Message delivered to {msg.topic()} [{msg.partition()}] at offset {msg.offset()}") - -def process_batch(batch, agg_mappings, thresholds, key): - """ - Process a batch of data and apply thresholds. - Args: batch (list of dict): List of messages from Kafka. - agg_mappings (dict): Mapping from threshold key to aggregation function. - thresholds (dict): Thresholds dictionary. - Returns: list of dict: Processed records ready to be sent to Kafka. - """ - if not batch: - LOGGER.info("Empty batch received. Skipping processing.") - return [] - - - df = pd.DataFrame(batch) - LOGGER.info(f"df {df} ") - df['time_stamp'] = pd.to_datetime(df['time_stamp'], errors='coerce') - df.dropna(subset=['time_stamp'], inplace=True) - LOGGER.info(f"df {df} ") - required_columns = {'time_stamp', 'kpi_id', 'kpi_value'} - if not required_columns.issubset(df.columns): - LOGGER.warning(f"Batch contains missing required columns. Required columns: {required_columns}. Skipping batch.") - return [] - if df.empty: - LOGGER.info("No data after filtering by KPI IDs. Skipping processing.") - return [] - - # Perform aggregations using named aggregation - try: - agg_dict = {key: value for key, value in agg_mappings.items()} - - df_agg_ = df.groupby(['window_start']).agg(**agg_dict).reset_index() - - #example: agg_dict = {'min_latency_E2E': ('kpi_value', 'min') - - #given that threshold has 1 value - second_value_tuple = next(iter(agg_dict.values()))[1] - #in case we have multiple thresholds! - #second_values_tuples = [value[1] for value in agg_dict.values()] - if second_value_tuple=="min": - df_agg = df_agg_.min(numeric_only=True).to_frame().T - elif second_value_tuple == "max": - df_agg = df_agg_.max(numeric_only=True).to_frame().T - elif second_value_tuple == "std": - df_agg = df_agg_.sted(numeric_only=True).to_frame().T - else: - df_agg = df_agg_.mean(numeric_only=True).to_frame().T - - # Assign the first value of window_start from the original aggregated data - df_agg['window_start'] = df_agg_['window_start'].iloc[0] - - # Reorder columns to place 'window_start' first if needed - cols = ['window_start'] + [col for col in df_agg.columns if col != 'window_start'] - df_agg = df_agg[cols] - - except Exception as e: - LOGGER.error(f"Aggregation error: {e}") - return [] - - # Apply thresholds - - - df_thresholded = ApplyThresholds(df_agg, thresholds) - df_thresholded['kpi_id'] = key - df_thresholded['window_start'] = df_thresholded['window_start'].dt.strftime('%Y-%m-%dT%H:%M:%SZ') - # Convert aggregated DataFrame to list of dicts - result = df_thresholded.to_dict(orient='records') - LOGGER.info(f"Processed batch with {len(result)} records after aggregation and thresholding.") - return result - -def produce_result(result, producer, destination_topic): - for record in result: - try: - producer.produce( - destination_topic, - key=str(record.get('kpi_id', '')), - value=json.dumps(record), - callback=delivery_report - ) - except KafkaException as e: - LOGGER.error(f"Failed to produce message: {e}") - producer.flush() - LOGGER.info(f"Produced {len(result)} aggregated records to '{destination_topic}'.") - -def DaskStreamer(key, kpi_list, thresholds, stop_event, - window_size="30s", time_stamp_col="time_stamp"): - client, cluster = initialize_dask_client() - consumer_conf = SettingKafkaConsumerParams() - consumer = Consumer(consumer_conf) - consumer.subscribe([KafkaTopic.VALUE.value]) - producer = initialize_kafka_producer() - - # Parse window_size to seconds - try: - window_size_td = pd.to_timedelta(window_size) - window_size_seconds = window_size_td.total_seconds() - except Exception as e: - LOGGER.error(f"Invalid window_size format: {window_size}. Error: {e}") - window_size_seconds = 30 - LOGGER.info(f"Batch processing interval set to {window_size_seconds} seconds.") - - # Extract aggregation mappings from thresholds - agg_mappings = GetAggregationMappings(thresholds) - if not agg_mappings: - LOGGER.error("No valid aggregation mappings extracted from thresholds. Exiting streamer.") - consumer.close() - producer.flush() - client.close() - cluster.close() - return - try: - batch = [] - last_batch_time = time.time() - LOGGER.info("Starting to consume messages...") - - while not stop_event.is_set(): - msg = consumer.poll(1.0) - - if msg is None: - current_time = time.time() - if (current_time - last_batch_time) >= window_size_seconds and batch: - LOGGER.info("Time-based batch threshold reached. Processing batch.") - future = client.submit(process_batch, batch, agg_mappings, thresholds) - future.add_done_callback(lambda fut: produce_result(fut.result(), producer, KafkaTopic.ALARMS.value)) - batch = [] - last_batch_time = current_time - continue - - if msg.error(): - if msg.error().code() == KafkaError._PARTITION_EOF: - LOGGER.warning(f"End of partition reached {msg.topic()} [{msg.partition()}] at offset {msg.offset()}") - else: - LOGGER.error(f"Kafka error: {msg.error()}") - continue - - try: - message_value = json.loads(msg.value().decode('utf-8')) - except json.JSONDecodeError as e: - LOGGER.error(f"JSON decode error: {e}") - continue - - try: - message_timestamp = pd.to_datetime(message_value[time_stamp_col], errors='coerce') - LOGGER.warning(f"message_timestamp: {message_timestamp}. Skipping message.") - - if pd.isna(message_timestamp): - LOGGER.warning(f"Invalid timestamp in message: {message_value}. Skipping message.") - continue - window_start = message_timestamp.floor(window_size) - LOGGER.warning(f"window_start: {window_start}. Skipping message.") - message_value['window_start'] = window_start - except Exception as e: - LOGGER.error(f"Error processing timestamp: {e}. Skipping message.") - continue - - if message_value['kpi_id'] not in kpi_list: - LOGGER.debug(f"KPI ID '{message_value['kpi_id']}' not in kpi_list. Skipping message.") - continue - - batch.append(message_value) - - current_time = time.time() - if (current_time - last_batch_time) >= window_size_seconds and batch: - LOGGER.info("Time-based batch threshold reached. Processing batch.") - future = client.submit(process_batch, batch, agg_mappings, thresholds, key) - future.add_done_callback(lambda fut: produce_result(fut.result(), producer, KafkaTopic.ALARMS.value)) - batch = [] - last_batch_time = current_time - - except Exception as e: - LOGGER.exception(f"Error in Dask streaming process: {e}") - finally: - # Process any remaining messages in the batch - if batch: - LOGGER.info("Processing remaining messages in the batch.") - future = client.submit(process_batch, batch, agg_mappings, thresholds) - future.add_done_callback(lambda fut: produce_result(fut.result(), producer, KafkaTopic.ALARMS.value)) - consumer.close() - producer.flush() - LOGGER.info("Kafka consumer and producer closed.") - client.close() - cluster.close() - LOGGER.info("Dask client and cluster closed.") diff --git a/src/analytics/backend/service/Streamer.py b/src/analytics/backend/service/Streamer.py new file mode 100644 index 0000000000000000000000000000000000000000..10917f002ed306ea408a20b92e94aed597ef1ea3 --- /dev/null +++ b/src/analytics/backend/service/Streamer.py @@ -0,0 +1,167 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 time +import json +import threading +import logging + +from confluent_kafka import KafkaException, KafkaError +from common.tools.kafka.Variables import KafkaTopic +from analytics.backend.service.AnalyzerHandlers import Handlers, aggregation_handler +from analytics.backend.service.AnalyzerHelper import AnalyzerHelper + + +logger = logging.getLogger(__name__) + + +class DaskStreamer(threading.Thread): + def __init__(self, key, input_kpis, output_kpis, thresholds, + batch_size = 5, + batch_duration = None, + window_size = None, + cluster_instance = None, + producer_instance = AnalyzerHelper.initialize_kafka_producer() + ): + super().__init__() + self.key = key + self.input_kpis = input_kpis + self.output_kpis = output_kpis + self.thresholds = thresholds + self.window_size = window_size # TODO: Not implemented + self.batch_size = batch_size + self.batch_duration = batch_duration + self.running = True + self.batch = [] + + # Initialize Kafka and Dask components + self.client = AnalyzerHelper.initialize_dask_client(cluster_instance) + self.consumer = AnalyzerHelper.initialize_kafka_consumer() # Single-threaded consumer + self.producer = producer_instance + + logger.info("Dask Streamer initialized.") + + def run(self): + """Main method to start the DaskStreamer.""" + try: + logger.info("Starting Dask Streamer") + last_batch_time = time.time() + while True: + if not self.consumer: + logger.warning("Kafka consumer is not initialized or stopped. Exiting loop.") + break + if not self.running: + logger.warning("Dask Streamer instance has been terminated. Exiting loop.") + break + if not self.client: + logger.warning("Dask client is not running. Exiting loop.") + break + message = self.consumer.poll(timeout=1.0) + if message is None: + # logger.info("No new messages received.") + continue + if message.error(): + if message.error().code() == KafkaError._PARTITION_EOF: + logger.warning(f"Consumer reached end of topic {message.topic()}/{message.partition()}") + elif message.error().code() == KafkaError.UNKNOWN_TOPIC_OR_PART: + logger.error(f"Subscribed topic {message.topic()} does not exist. May be topic does not have any messages.") + continue + elif message.error(): + raise KafkaException(message.error()) + else: + try: + value = json.loads(message.value()) + except json.JSONDecodeError: + logger.error(f"Failed to decode message: {message.value()}") + continue + self.batch.append(value) + + # Window size has a precedence over batch size + if self.batch_duration is None: + if len(self.batch) >= self.batch_size: # If batch size is not provided, process continue with the default batch size + logger.info(f"Processing based on batch size {self.batch_size}.") + self.task_handler_selector() + self.batch = [] + else: + # Process based on window size + current_time = time.time() + if (current_time - last_batch_time) >= self.batch_duration and self.batch: + logger.info(f"Processing based on window size {self.batch_duration}.") + self.task_handler_selector() + self.batch = [] + last_batch_time = current_time + + except Exception as e: + logger.exception(f"Error in Dask streaming process: {e}") + finally: + self.stop() + logger.info(">>> Exiting Dask Streamer...") + + def task_handler_selector(self): + """Select the task handler based on the task type.""" + logger.info(f"Batch to be processed: {self.batch}") + if Handlers.is_valid_handler(self.thresholds["task_type"]): + if self.client is not None and self.client.status == 'running': + try: + future = self.client.submit(aggregation_handler, "batch size", self.key, + self.batch, self.input_kpis, self.output_kpis, self.thresholds) + future.add_done_callback(lambda fut: self.produce_result(fut.result(), KafkaTopic.ALARMS.value)) + except Exception as e: + logger.error(f"Failed to submit task to Dask client or unable to process future. See error for detail: {e}") + else: + logger.warning("Dask client is not running. Skipping processing.") + else: + logger.warning(f"Unknown task type: {self.thresholds['task_type']}. Skipping processing.") + + def produce_result(self, result, destination_topic): + """Produce results to the Kafka topic.""" + if not result: + logger.warning("Nothing to produce. Skipping.") + return + for record in result: + try: + self.producer.produce( + destination_topic, + key=str(record.get('kpi_id', '')), + value=json.dumps(record), + callback=AnalyzerHelper.delivery_report + ) + except KafkaException as e: + logger.error(f"Failed to produce message: {e}") + self.producer.flush() + logger.info(f"Produced {len(result)} aggregated records to '{destination_topic}'.") + + def stop(self): + """Clean up Kafka and Dask thread resources.""" + if not self.running: + logger.info("Dask Streamer is already stopped.") + return + self.running = False + logger.info("Streamer running status is set to False. Waiting 5 seconds before stopping...") + time.sleep(5) # Waiting time for running tasks to complete + if self.consumer: + try: + self.consumer.close() + logger.info("Kafka consumer closed.") + except Exception as e: + logger.error(f"Error closing Kafka consumer: {e}") + + if self.client is not None and hasattr(self.client, 'status') and self.client.status == 'running': + try: + self.client.close() + logger.info("Dask client closed.") + except Exception as e: + logger.error(f"Error closing Dask client: {e}") + +# TODO: May be Single streamer for all analyzers ... ? diff --git a/src/analytics/backend/tests/messages.py b/src/analytics/backend/tests/messages.py index 55d966dfbbae2318a5f774049b02f5b340070113..2ff1e93536e94863ba9eaaf76d35f7453ba95727 100644 --- a/src/analytics/backend/tests/messages.py +++ b/src/analytics/backend/tests/messages.py @@ -53,8 +53,8 @@ def create_analyzer(): _create_analyzer.algorithm_name = "Test_Aggergate_and_Threshold" _create_analyzer.operation_mode = AnalyzerOperationMode.ANALYZEROPERATIONMODE_STREAMING - _kpi_id = KpiId() # input IDs to analyze + _kpi_id = KpiId() _kpi_id.kpi_id.uuid = str(uuid.uuid4()) _kpi_id.kpi_id.uuid = "5716c369-932b-4a02-b4c7-6a2e808b92d7" _create_analyzer.input_kpi_ids.append(_kpi_id) @@ -63,11 +63,14 @@ def create_analyzer(): _create_analyzer.input_kpi_ids.append(_kpi_id) _kpi_id.kpi_id.uuid = str(uuid.uuid4()) _create_analyzer.input_kpi_ids.append(_kpi_id) + # output IDs after analysis + _kpi_id = KpiId() _kpi_id.kpi_id.uuid = str(uuid.uuid4()) _create_analyzer.output_kpi_ids.append(_kpi_id) _kpi_id.kpi_id.uuid = str(uuid.uuid4()) _create_analyzer.output_kpi_ids.append(_kpi_id) + # parameter _threshold_dict = { # 'avg_value' :(20, 30), 'min_value' :(00, 10), 'max_value' :(45, 50), diff --git a/src/analytics/backend/tests/messages_analyzer.py b/src/analytics/backend/tests/messages_analyzer.py new file mode 100644 index 0000000000000000000000000000000000000000..813b4f06cdbaf1dd1c00c4d0f0dfa4b78339e09c --- /dev/null +++ b/src/analytics/backend/tests/messages_analyzer.py @@ -0,0 +1,67 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 pandas as pd +from analytics.backend.service.AnalyzerHandlers import Handlers + +def get_input_kpi_list(): + return ["1e22f180-ba28-4641-b190-2287bf446666", "6e22f180-ba28-4641-b190-2287bf448888", 'kpi_3'] + +def get_output_kpi_list(): + return ["1e22f180-ba28-4641-b190-2287bf441616", "6e22f180-ba28-4641-b190-2287bf181818", 'kpi_4'] + +def get_thresholds(): + return { + "task_type": Handlers.AGGREGATION_HANDLER.value, + "task_parameter": [ + {"last": [40, 80], "variance": [300, 500]}, + {"count": [2, 4], "max": [70, 100]}, + {"min": [10, 20], "avg": [50, 70]}, + ], + } + +def get_duration(): + return 90 + +def get_batch_duration(): + return 30 + +def get_windows_size(): + return None + +def get_batch_size(): + return 5 + +def get_interval(): + return 5 + +def get_batch(): + return [ + {"time_stamp": "2025-01-13T08:44:10Z", "kpi_id": "6e22f180-ba28-4641-b190-2287bf448888", "kpi_value": 46.72}, + {"time_stamp": "2025-01-13T08:44:12Z", "kpi_id": "6e22f180-ba28-4641-b190-2287bf448888", "kpi_value": 65.22}, + {"time_stamp": "2025-01-13T08:44:14Z", "kpi_id": "1e22f180-ba28-4641-b190-2287bf446666", "kpi_value": 54.24}, + {"time_stamp": "2025-01-13T08:44:16Z", "kpi_id": "1e22f180-ba28-4641-b190-2287bf446666", "kpi_value": 57.67}, + {"time_stamp": "2025-01-13T08:44:18Z", "kpi_id": "1e22f180-ba28-4641-b190-2287bf446666", "kpi_value": 38.6}, + {"time_stamp": "2025-01-13T08:44:20Z", "kpi_id": "6e22f180-ba28-4641-b190-2287bf448888", "kpi_value": 38.9}, + {"time_stamp": "2025-01-13T08:44:22Z", "kpi_id": "6e22f180-ba28-4641-b190-2287bf448888", "kpi_value": 52.44}, + {"time_stamp": "2025-01-13T08:44:24Z", "kpi_id": "6e22f180-ba28-4641-b190-2287bf448888", "kpi_value": 47.76}, + {"time_stamp": "2025-01-13T08:44:26Z", "kpi_id": "efef4d95-1cf1-43c4-9742-95c283ddd7a6", "kpi_value": 33.71}, + {"time_stamp": "2025-01-13T08:44:28Z", "kpi_id": "efef4d95-1cf1-43c4-9742-95c283ddd7a6", "kpi_value": 64.44}, + ] + +def get_agg_df(): + data = [ + {"kpi_id": "1e22f180-ba28-4641-b190-2287bf441616", "last": 47.76, "variance": 970.41}, + ] + return pd.DataFrame(data) diff --git a/src/analytics/backend/tests/test_backend.py b/src/analytics/backend/tests/test_backend.py index 4aa9df5fae9849ee429361603a35b2fb8eaa4d23..1bbfaee136942bb7f14e77438683e8460f0a4f0b 100644 --- a/src/analytics/backend/tests/test_backend.py +++ b/src/analytics/backend/tests/test_backend.py @@ -12,148 +12,307 @@ # See the License for the specific language governing permissions and # limitations under the License. -import time, json -from typing import Dict +import time +import json +import pytest import logging -from threading import Event, Thread -from common.tools.kafka.Variables import KafkaTopic +import pandas as pd + +from unittest.mock import MagicMock, patch +from .messages_analyzer import get_batch, get_input_kpi_list, get_output_kpi_list, get_thresholds, \ + get_windows_size, get_batch_size, get_agg_df, get_duration + +from common.tools.kafka.Variables import KafkaTopic +from analytics.backend.service.Streamer import DaskStreamer +from analytics.backend.service.AnalyzerHandlers import aggregation_handler, threshold_handler from analytics.backend.service.AnalyticsBackendService import AnalyticsBackendService -from analytics.backend.tests.messages import get_kpi_id_list, get_operation_list, get_threshold_dict -from .messages import create_analyzer, create_analyzer_dask -from threading import Thread, Event -from ..service.DaskStreaming import DaskStreamer -LOGGER = logging.getLogger(__name__) +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(funcName)s - %(levelname)s - %(message)s') + + +# ---- +# Test fixtures and helper functions +# ---- + +@pytest.fixture(autouse=True) +def log_all_methods(request): + ''' + This fixture logs messages before and after each test function runs, indicating the start and end of the test. + The autouse=True parameter ensures that this logging happens automatically for all tests in the module. + ''' + logger.info(f" >>>>> Starting test: {request.node.name} ") + yield + logger.info(f" <<<<< Finished test: {request.node.name} ") + +@pytest.fixture +def mock_kafka_producer(): + mock_producer = MagicMock() + mock_producer.produce = MagicMock() + mock_producer.flush = MagicMock() + return mock_producer + +@pytest.fixture +def mock_dask_cluster(): + mock_cluster = MagicMock() + mock_cluster.close = MagicMock() + return mock_cluster + +@pytest.fixture +def mock_dask_client(): + mock_client = MagicMock() + mock_client.status = 'running' + mock_client.submit = MagicMock() + return mock_client + +@pytest.fixture() +def mock_kafka_consumer(): + mock_consumer = MagicMock() + mock_consumer.subscribe = MagicMock() + return mock_consumer + +@pytest.fixture() +def mock_streamer_start(): + mock_streamer = MagicMock() + mock_streamer.start = MagicMock() + return mock_streamer ########################### -# Tests Implementation of Telemetry Backend +# funtionality pytest cases with specific fixtures for AnalyticsBackendService class sub-methods ########################### -# --- "test_validate_kafka_topics" should be run before the functionality tests --- -def test_validate_kafka_topics(): - LOGGER.debug(" >>> test_validate_kafka_topics: START <<< ") - response = KafkaTopic.create_all_topics() - assert isinstance(response, bool) - - -# --- To test Dask Streamer functionality --- -# def test_StartDaskStreamer(): # Directly from the Streamer class -# LOGGER.debug(" >>> test_StartSparkStreamer: START <<< ") -# stop_event = Event() -# kpi_list = ["1e22f180-ba28-4641-b190-2287bf446666", "6e22f180-ba28-4641-b190-2287bf448888", 'kpi_3'] -# oper_list = ['avg', 'min', 'max',] -# thresholds = { -# 'avg_value': (10.0, 90.0), -# 'min_value': (5.0, 95.0), -# 'max_value': (15.0, 85.0), -# 'latency' : (2.0, 10.0) -# } - -# # Start the DaskStreamer in a separate thread -# streamer_thread = Thread( -# target=DaskStreamer, -# args=("analytics_stream", kpi_list, oper_list, thresholds, stop_event), -# kwargs={ -# "window_size": "60s", -# "win_slide_duration": "30s", -# "time_stamp_col": "time_stamp" -# } -# ) -# streamer_thread.start() -# try: -# while True: -# time.sleep(10) -# except KeyboardInterrupt: -# LOGGER.info("KeyboardInterrupt received. Stopping streamer...") -# stop_event.set() -# streamer_thread.join() -# LOGGER.info("Streamer stopped gracefully.") - -# --- To test Start Streamer functionality --- -# def test_StartDaskStreamer(): -# LOGGER.debug(" >>> test_StartBaskStreamer: START <<< ") -# analyzer_obj = create_analyzer_dask() -# # LOGGER.info("Created Analyzer Object: {:}".format(analyzer_obj)) -# analyzer_uuid = analyzer_obj.analyzer_id.analyzer_id.uuid -# analyzer_to_generate : Dict = { -# "algo_name" : analyzer_obj.algorithm_name, -# "input_kpis" : [k.kpi_id.uuid for k in analyzer_obj.input_kpi_ids], -# "output_kpis" : [k.kpi_id.uuid for k in analyzer_obj.output_kpi_ids], -# "oper_mode" : analyzer_obj.operation_mode, -# "thresholds" : json.loads(analyzer_obj.parameters["thresholds"]), -# "oper_list" : json.loads(analyzer_obj.parameters["oper_list"]), -# # "oper_list" : analyzer_obj.parameters["oper_list"], -# "window_size" : analyzer_obj.parameters["window_size"], -# "window_slider" : analyzer_obj.parameters["window_slider"], -# # "store_aggregate" : analyzer_obj.parameters["store_aggregate"] -# } -# AnalyticsBackendServiceObj = AnalyticsBackendService() -# LOGGER.info("Analyzer to be generated: {:}".format((analyzer_to_generate))) -# response = AnalyticsBackendServiceObj.StartDaskListener(analyzer_uuid, analyzer_to_generate) -# assert isinstance(response, bool) -# time.sleep(100) -# LOGGER.info('Initiating StopRequestListener...') -# # AnalyticsBackendServiceObj = AnalyticsBackendService() -# response = AnalyticsBackendServiceObj.StopDaskListener(analyzer_uuid) -# LOGGER.debug(str(response)) -# assert isinstance(response, bool) +@pytest.fixture +def analytics_service(mock_kafka_producer, mock_dask_cluster, mock_dask_client, mock_kafka_consumer, mock_streamer_start): + with patch('analytics.backend.service.AnalyzerHelper.AnalyzerHelper.initialize_kafka_producer', return_value = mock_kafka_producer), \ + patch('analytics.backend.service.AnalyzerHelper.AnalyzerHelper.initialize_dask_cluster', return_value = mock_dask_cluster ), \ + patch('analytics.backend.service.AnalyzerHelper.AnalyzerHelper.initialize_dask_client', return_value = mock_dask_client ), \ + patch('analytics.backend.service.AnalyzerHelper.AnalyzerHelper.initialize_kafka_consumer', return_value = mock_kafka_consumer), \ + patch('analytics.backend.service.Streamer.DaskStreamer.run', return_value = mock_streamer_start): + + service = AnalyticsBackendService() + yield service + service.close() -# --- To test Start Streamer functionality --- -# def test_StartSparkStreamer(): -# LOGGER.debug(" >>> test_StartSparkStreamer: START <<< ") -# analyzer_obj = create_analyzer() -# analyzer_uuid = analyzer_obj.analyzer_id.analyzer_id.uuid -# analyzer_to_generate : Dict = { -# "algo_name" : analyzer_obj.algorithm_name, -# "input_kpis" : [k.kpi_id.uuid for k in analyzer_obj.input_kpi_ids], -# "output_kpis" : [k.kpi_id.uuid for k in analyzer_obj.output_kpi_ids], -# "oper_mode" : analyzer_obj.operation_mode, -# "thresholds" : json.loads(analyzer_obj.parameters["thresholds"]), -# "window_size" : analyzer_obj.parameters["window_size"], -# "window_slider" : analyzer_obj.parameters["window_slider"], -# # "store_aggregate" : analyzer_obj.parameters["store_aggregate"] -# } -# AnalyticsBackendServiceObj = AnalyticsBackendService() -# response = AnalyticsBackendServiceObj.StartSparkStreamer(analyzer_uuid, analyzer_to_generate) -# assert isinstance(response, bool) +@pytest.fixture +def analyzer_data(): + return { + 'algo_name' : 'test_algorithm', + 'oper_mode' : 'test_mode', + 'input_kpis' : get_input_kpi_list(), + 'output_kpis' : get_output_kpi_list(), + 'thresholds' : get_thresholds(), + 'duration' : get_duration(), + 'batch_size_min' : get_batch_size(), + 'window_size' : get_windows_size(), + 'batch_duration_min' : get_duration(), + } -# --- To TEST StartRequestListenerFunctionality -# def test_StartRequestListener(): -# LOGGER.info('test_RunRequestListener') -# AnalyticsBackendServiceObj = AnalyticsBackendService() -# AnalyticsBackendServiceObj.stop_event = Event() -# listener_thread = Thread(target=AnalyticsBackendServiceObj.RequestListener, args=()) -# listener_thread.start() - -# time.sleep(100) - - # AnalyticsBackendServiceObj.stop_event.set() - # LOGGER.info('Backend termination initiated. waiting for termination... 10 seconds') - # listener_thread.join(timeout=10) - # assert not listener_thread.is_alive(), "RequestListener thread did not terminate as expected." - # LOGGER.info('Completed test_RunRequestListener') - -# To test START and STOP communication together -# def test_StopRequestListener(): -# LOGGER.info('test_RunRequestListener') -# LOGGER.info('Initiating StartRequestListener...') -# AnalyticsBackendServiceObj = AnalyticsBackendService() -# response_thread = AnalyticsBackendServiceObj.StartRequestListener() # response is Tuple (thread, stop_event) -# # LOGGER.debug(str(response_thread)) -# time.sleep(10) -# LOGGER.info('Initiating StopRequestListener...') -# AnalyticsBackendServiceObj = AnalyticsBackendService() -# response = AnalyticsBackendServiceObj.StopRequestListener(response_thread) -# LOGGER.debug(str(response)) -# assert isinstance(response, bool) +def test_start_streamer(analytics_service, analyzer_data): + analyzer_uuid = "test-analyzer-uuid" + # Start streamer + result = analytics_service.StartStreamer(analyzer_uuid, analyzer_data) + assert result is True + assert analyzer_uuid in analytics_service.active_streamers + +def test_stop_streamer(analytics_service, analyzer_data): + analyzer_uuid = "test-analyzer-uuid" + + # Start streamer for stopping it later + analytics_service.StartStreamer(analyzer_uuid, analyzer_data) + assert analyzer_uuid in analytics_service.active_streamers + + # Stop streamer + with patch('time.sleep', return_value=None): + result = analytics_service.StopStreamer(analyzer_uuid) + assert result is True + assert analyzer_uuid not in analytics_service.active_streamers + + # Verify that the streamer was stopped + assert analyzer_uuid not in analytics_service.active_streamers + +def test_close(analytics_service, mock_kafka_producer, mock_dask_cluster): + analytics_service.close() + + mock_kafka_producer.flush.assert_called_once() + mock_dask_cluster.close.assert_called_once() + +########################### +# funtionality pytest with specific fixtures for streamer class sub methods +########################### + +@pytest.fixture +def dask_streamer(mock_kafka_producer, mock_dask_cluster, mock_dask_client, mock_kafka_consumer): + with patch('analytics.backend.service.AnalyzerHelper.AnalyzerHelper.initialize_kafka_producer', return_value = mock_kafka_producer), \ + patch('analytics.backend.service.AnalyzerHelper.AnalyzerHelper.initialize_dask_cluster', return_value = mock_dask_cluster ), \ + patch('analytics.backend.service.AnalyzerHelper.AnalyzerHelper.initialize_dask_client', return_value = mock_dask_client ), \ + patch('analytics.backend.service.AnalyzerHelper.AnalyzerHelper.initialize_kafka_consumer', return_value = mock_kafka_consumer): + + return DaskStreamer( + key = "test_key", + input_kpis = get_input_kpi_list(), + output_kpis = get_output_kpi_list(), + thresholds = get_thresholds(), + batch_size = get_batch_size(), + window_size = get_windows_size(), + cluster_instance = mock_dask_cluster(), + producer_instance = mock_kafka_producer(), + ) + +def test_dask_streamer_initialization(dask_streamer): + """Test if the DaskStreamer initializes correctly.""" + assert dask_streamer.key == "test_key" + assert dask_streamer.batch_size == get_batch_size() + assert dask_streamer.window_size is None + assert dask_streamer.consumer is not None + assert dask_streamer.producer is not None + assert dask_streamer.client is not None + +def test_run_stops_on_no_consumer(dask_streamer): + """Test if the run method exits when the consumer is not initialized.""" + dask_streamer.consumer = None + with patch('time.sleep', return_value=None): + dask_streamer.run() -# To independently tests the SparkListener functionality -# def test_SparkListener(): -# LOGGER.info('test_RunRequestListener') -# AnalyticsBackendServiceObj = AnalyticsBackendService() -# response = AnalyticsBackendServiceObj.RunSparkStreamer( -# get_kpi_id_list(), get_operation_list(), get_threshold_dict() -# ) -# LOGGER.debug(str(response)) + assert not dask_streamer.running + +def test_task_handler_selector_valid_handler(dask_streamer, mock_dask_client): + """Test task handler selection with a valid handler.""" + with patch('analytics.backend.service.AnalyzerHelper.AnalyzerHelper.initialize_dask_client', return_value = mock_dask_client): + + dask_streamer.task_handler_selector() + assert dask_streamer.client.status == 'running' + +def test_task_handler_selector_invalid_handler(dask_streamer): + """Test task handler selection with an invalid handler.""" + with patch('analytics.backend.service.AnalyzerHandlers.Handlers.is_valid_handler', return_value=False): + dask_streamer.task_handler_selector() + assert dask_streamer.batch == [] + +def test_produce_result(dask_streamer): + """Test if produce_result sends records to Kafka.""" + result = [{"kpi_id": "kpi1", "value": 100}] + with patch('analytics.backend.service.AnalyzerHelper.AnalyzerHelper.delivery_report', return_value=None) as mock_delivery_report, \ + patch.object(dask_streamer.producer, 'produce') as mock_produce: + dask_streamer.produce_result(result, "test_topic") + mock_produce.assert_called_once_with( + "test_topic", + key="kpi1", + value=json.dumps({"kpi_id": "kpi1", "value": 100}), + callback=mock_delivery_report + ) + +def test_stop(dask_streamer): + """Test the cleanup method.""" + with patch.object(dask_streamer.consumer, 'close') as mock_consumer_close, \ + patch.object(dask_streamer.client, 'close') as mock_client_close, \ + patch('time.sleep', return_value=0): + + # Mock the conditions required for the close calls + dask_streamer.client.status = 'running' + + dask_streamer.stop() + + mock_consumer_close.assert_called_once() + mock_client_close.assert_called_once() + +def test_run_with_valid_consumer(dask_streamer): + """Test the run method with a valid Kafka consumer.""" + with patch.object(dask_streamer.consumer, 'poll') as mock_poll, \ + patch.object(dask_streamer, 'task_handler_selector') as mock_task_handler_selector: + + # Simulate valid messages without errors + mock_message_1 = MagicMock() + mock_message_1.value.return_value = b'{"kpi_id": "kpi1", "value": 100}' + mock_message_1.error.return_value = None # No error + + mock_message_2 = MagicMock() + mock_message_2.value.return_value = b'{"kpi_id": "kpi2", "value": 200}' + mock_message_2.error.return_value = None # No error + + # Mock `poll` to return valid messages + mock_poll.side_effect = [mock_message_1, mock_message_2] + + # Run the `run` method in a limited loop + with patch('time.sleep', return_value=None): # Mock `sleep` to avoid delays + dask_streamer.running = True + dask_streamer.batch_size = 2 + + # Limit the loop by breaking it after one full processing cycle + def stop_running_after_task_handler(): + logger.info("Stopping the streamer after processing the first batch.") + dask_streamer.running = False + + mock_task_handler_selector.side_effect = stop_running_after_task_handler + dask_streamer.run() + + assert len(dask_streamer.batch) == 0 # Batch should be cleared after processing + mock_task_handler_selector.assert_called_once() # Task handler should be called once + mock_poll.assert_any_call(timeout=1.0) # Poll should have been called at least once + +# # add a test to check the working of aggregation_handler function and threshold_handler from AnalyzerHandlers.py +def test_aggregation_handler(): + + # Create a sample batch + batch = get_batch() + input_kpi_list = get_input_kpi_list() + output_kpi_list = get_output_kpi_list() + thresholds = get_thresholds() + + # Test aggregation_handler + aggregated_df = aggregation_handler( + "test_batch", "test_key", batch, input_kpi_list, output_kpi_list, thresholds + ) + assert isinstance(aggregated_df, list) + assert all(isinstance(item, dict) for item in aggregated_df) + +# # Test threshold_handler +def test_threshold_handler(): + # Create a sample aggregated DataFrame + agg_df = get_agg_df() + thresholds = get_thresholds() + + # Test threshold_handler + result = threshold_handler("test_key", agg_df, thresholds["task_parameter"][0]) + + assert isinstance(result, pd.DataFrame) + assert result.shape == (1, 7) + + +########################### +# integration test of Streamer with backend service (Shouldn't be run in the CI/CD pipeline) +########################### +# This is a local machine test to check the integration of the backend service with the Streamer + +# @pytest.fixture(scope='session') +# def analyticBackend_service(): +# logger.info('Initializing AnalyticsBackendService...') + +# _service = AnalyticsBackendService() +# _service.start() + +# logger.info('Yielding AnalyticsBackendService...') +# yield _service + +# logger.info('Terminating AnalyticsBackendService...') +# _service.stop() +# logger.info('Terminated AnalyticsBackendService...') + + +# # --- "test_validate_kafka_topics" should be run before the functionality tests --- +# def test_validate_kafka_topics(): +# logger.debug(" >>> test_validate_kafka_topics: START <<< ") +# response = KafkaTopic.create_all_topics() # assert isinstance(response, bool) + +# def test_backend_integration_with_frontend(analyticBackend_service: AnalyticsBackendService): +# # backendServiceObject = AnalyticsBackendService() +# # backendServiceObject.install_servicers() +# logger.info(" waiting for 2 minutes for the backend service before termination ... ") +# time.sleep(300) +# logger.info(" Initiating stop collector ... ") +# status = analyticBackend_service.StopStreamer("efef4d95-1cf1-43c4-9742-95c283ddd666") +# analyticBackend_service.close() +# assert isinstance(status, bool) +# assert status == True +# logger.info(" Backend service terminated successfully ... ") diff --git a/src/analytics/frontend/service/AnalyticsFrontendServiceServicerImpl.py b/src/analytics/frontend/service/AnalyticsFrontendServiceServicerImpl.py index fd5bcd185b1f9945eccf583c33af2a243fe729be..cd20503e7dbe1059b2209e4b0ccd29a229e7916e 100644 --- a/src/analytics/frontend/service/AnalyticsFrontendServiceServicerImpl.py +++ b/src/analytics/frontend/service/AnalyticsFrontendServiceServicerImpl.py @@ -46,7 +46,7 @@ class AnalyticsFrontendServiceServicerImpl(AnalyticsFrontendServiceServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def StartAnalyzer(self, - request : Analyzer, grpc_context: grpc.ServicerContext # type: ignore + request : Analyzer, context: grpc.ServicerContext # type: ignore ) -> AnalyzerId: # type: ignore LOGGER.info ("At Service gRPC message: {:}".format(request)) response = AnalyzerId() @@ -65,14 +65,18 @@ class AnalyticsFrontendServiceServicerImpl(AnalyticsFrontendServiceServicer): """ analyzer_uuid = analyzer_obj.analyzer_id.analyzer_id.uuid analyzer_to_generate : Dict = { - "algo_name" : analyzer_obj.algorithm_name, - "input_kpis" : [k.kpi_id.uuid for k in analyzer_obj.input_kpi_ids], - "output_kpis" : [k.kpi_id.uuid for k in analyzer_obj.output_kpi_ids], - "oper_mode" : analyzer_obj.operation_mode, - "thresholds" : json.loads(analyzer_obj.parameters["thresholds"]), - "window_size" : analyzer_obj.parameters["window_size"], - "window_slider" : analyzer_obj.parameters["window_slider"], - # "store_aggregate" : analyzer_obj.parameters["store_aggregate"] + "algo_name" : analyzer_obj.algorithm_name, + "input_kpis" : [k.kpi_id.uuid for k in analyzer_obj.input_kpi_ids], + "output_kpis" : [k.kpi_id.uuid for k in analyzer_obj.output_kpi_ids], + "oper_mode" : analyzer_obj.operation_mode, + "duration" : analyzer_obj.duration_s, + "thresholds" : json.loads(analyzer_obj.parameters["thresholds"]), + "window_size" : analyzer_obj.parameters["window_size"], # slider window size in seconds (single batch execution time) + "window_slider" : analyzer_obj.parameters["window_slider"], # slider shift in seconds + "batch_size_min" : analyzer_obj.batch_min_size, # currently implemented + "batch_size_max" : analyzer_obj.batch_max_size, + "batch_duration_min" : analyzer_obj.batch_min_duration_s, # currently implemented + "batch_interval_max" : analyzer_obj.batch_max_duration_s } self.kafka_producer.produce( KafkaTopic.ANALYTICS_REQUEST.value, @@ -137,7 +141,7 @@ class AnalyticsFrontendServiceServicerImpl(AnalyticsFrontendServiceServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def StopAnalyzer(self, - request : AnalyzerId, grpc_context: grpc.ServicerContext # type: ignore + request : AnalyzerId, context: grpc.ServicerContext # type: ignore ) -> Empty: # type: ignore LOGGER.info ("At Service gRPC message: {:}".format(request)) try: @@ -181,7 +185,7 @@ class AnalyticsFrontendServiceServicerImpl(AnalyticsFrontendServiceServicer): @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def SelectAnalyzers(self, - filter : AnalyzerFilter, contextgrpc_context: grpc.ServicerContext # type: ignore + filter : AnalyzerFilter, context: grpc.ServicerContext # type: ignore ) -> AnalyzerList: # type: ignore LOGGER.info("At Service gRPC message: {:}".format(filter)) response = AnalyzerList() @@ -202,7 +206,5 @@ class AnalyticsFrontendServiceServicerImpl(AnalyticsFrontendServiceServicer): def delivery_callback(self, err, msg): if err: LOGGER.debug('Message delivery failed: {:}'.format(err)) - # print ('Message delivery failed: {:}'.format(err)) else: LOGGER.debug('Message delivered to topic {:}'.format(msg.topic())) - # print('Message delivered to topic {:}'.format(msg.topic())) diff --git a/src/analytics/frontend/tests/messages.py b/src/analytics/frontend/tests/messages.py index 4df6070bedffd91402953bbbbbec16ce0118008c..326bc0be22c0d0e01ccdd79b439b82a88d06e0ad 100644 --- a/src/analytics/frontend/tests/messages.py +++ b/src/analytics/frontend/tests/messages.py @@ -20,43 +20,77 @@ from common.proto.analytics_frontend_pb2 import ( AnalyzerOperationMode, Analyze def create_analyzer_id(): _create_analyzer_id = AnalyzerId() - # _create_analyzer_id.analyzer_id.uuid = str(uuid.uuid4()) + _create_analyzer_id.analyzer_id.uuid = str(uuid.uuid4()) # _create_analyzer_id.analyzer_id.uuid = "efef4d95-1cf1-43c4-9742-95c283ddd7a6" - _create_analyzer_id.analyzer_id.uuid = "1e22f180-ba28-4641-b190-2287bf446666" + # _create_analyzer_id.analyzer_id.uuid = "1e22f180-ba28-4641-b190-2287bf446666" return _create_analyzer_id def create_analyzer(): _create_analyzer = Analyzer() - # _create_analyzer.analyzer_id.analyzer_id.uuid = str(uuid.uuid4()) - _create_analyzer.analyzer_id.analyzer_id.uuid = "1e22f180-ba28-4641-b190-2287bf446666" - _create_analyzer.algorithm_name = "Test_Aggergate_and_Threshold" + + _create_analyzer.analyzer_id.analyzer_id.uuid = str(uuid.uuid4()) + # _create_analyzer.analyzer_id.analyzer_id.uuid = "1e22f180-ba28-4641-b190-2287bf446666" + _create_analyzer.algorithm_name = "Test_new_Threshold" _create_analyzer.operation_mode = AnalyzerOperationMode.ANALYZEROPERATIONMODE_STREAMING - _kpi_id = KpiId() # input IDs to analyze - _kpi_id.kpi_id.uuid = str(uuid.uuid4()) + _kpi_id = KpiId() + + # _kpi_id.kpi_id.uuid = str(uuid.uuid4()) _kpi_id.kpi_id.uuid = "6e22f180-ba28-4641-b190-2287bf448888" _create_analyzer.input_kpi_ids.append(_kpi_id) - _kpi_id.kpi_id.uuid = str(uuid.uuid4()) + + # _kpi_id.kpi_id.uuid = str(uuid.uuid4()) _kpi_id.kpi_id.uuid = "1e22f180-ba28-4641-b190-2287bf446666" _create_analyzer.input_kpi_ids.append(_kpi_id) + _kpi_id.kpi_id.uuid = str(uuid.uuid4()) _create_analyzer.input_kpi_ids.append(_kpi_id) + # output IDs after analysis - _kpi_id.kpi_id.uuid = str(uuid.uuid4()) + _kpi_id = KpiId() + + # _kpi_id.kpi_id.uuid = str(uuid.uuid4()) + _kpi_id.kpi_id.uuid = "6e22f180-ba28-4641-b190-2287bf181818" + _create_analyzer.output_kpi_ids.append(_kpi_id) + + # _kpi_id.kpi_id.uuid = str(uuid.uuid4()) + _kpi_id.kpi_id.uuid = "1e22f180-ba28-4641-b190-2287bf441616" _create_analyzer.output_kpi_ids.append(_kpi_id) + _kpi_id.kpi_id.uuid = str(uuid.uuid4()) _create_analyzer.output_kpi_ids.append(_kpi_id) + # parameter + # _threshold_dict = { + # 'mean_value' :[20, 30], 'min_value' :[00, 10], 'max_value' :[45, 50], + # 'first_value' :[00, 10], 'last_value' :[40, 50], 'std_value' :[00, 10] + # } _threshold_dict = { - 'mean_value' :(20, 30), 'min_value' :(00, 10), 'max_value' :(45, 50), - 'first_value' :(00, 10), 'last_value' :(40, 50), 'std_value':(00, 10) - } + "task_type": Handlers.AGGREGATION_HANDLER.value, + "task_parameter": [ + {"last": [40, 80], "variance": [300, 500]}, + {"count": [2, 4], "max": [70, 100]}, + {"min": [10, 20], "avg": [50, 70]}, + ], + } + _create_analyzer.parameters['thresholds'] = json.dumps(_threshold_dict) - _create_analyzer.parameters['window_size'] = "10s" # Such as "10 seconds", "2 minutes", "3 hours", "4 days" or "5 weeks" - _create_analyzer.parameters['window_slider'] = "5s" # should be less than window size - _create_analyzer.parameters['store_aggregate'] = str(False) # TRUE to store. No implemented yet + _create_analyzer.parameters['window_size'] = "0" # slider window size in seconds (Total time for aggeration processing) + _create_analyzer.parameters['window_slider'] = "0" # should be less than window size + _create_analyzer.parameters['store_aggregate'] = str(False) # TRUE to store. No implemented yet + # duration of the analyzer + _create_analyzer.duration_s = 90 + + # batch window size + _create_analyzer.batch_min_duration_s = 20 + _create_analyzer.batch_max_duration_s = 50 + + # batch size + _create_analyzer.batch_min_size = 5 + _create_analyzer.batch_max_size = 10 + return _create_analyzer def create_analyzer_filter(): @@ -84,3 +118,10 @@ def create_analyzer_filter(): # _create_analyzer_filter.input_kpi_ids.append(_output_kpi_id_obj) return _create_analyzer_filter + + +# Added for testing to remove the dependency on the backend service +from enum import Enum +class Handlers(Enum): + AGGREGATION_HANDLER = "AggregationHandler" + UNSUPPORTED_HANDLER = "UnsupportedHandler" diff --git a/src/analytics/frontend/tests/test_frontend.py b/src/analytics/frontend/tests/test_frontend.py index 134871fb77719e4747b6fc3ae6cfd21dd317a31f..7d8a08d3ad2d82758b088a8f83342c2b3929eadd 100644 --- a/src/analytics/frontend/tests/test_frontend.py +++ b/src/analytics/frontend/tests/test_frontend.py @@ -78,6 +78,15 @@ def analyticsFrontend_client(analyticsFrontend_service : AnalyticsFrontendServic LOGGER.info('Closed AnalyticsFrontendClient...') +@pytest.fixture(autouse=True) +def log_all_methods(request): + ''' + This fixture logs messages before and after each test function runs, indicating the start and end of the test. + The autouse=True parameter ensures that this logging happens automatically for all tests in the module. + ''' + LOGGER.info(f" >>>>> Starting test: {request.node.name} ") + yield + LOGGER.info(f" <<<<< Finished test: {request.node.name} ") ########################### # Tests Implementation of Analytics Frontend @@ -89,24 +98,17 @@ def test_validate_kafka_topics(): response = KafkaTopic.create_all_topics() assert isinstance(response, bool) -# ----- core funtionality test ----- -# def test_StartAnalytics(analyticsFrontend_client): -# LOGGER.info(' >>> test_StartAnalytic START: <<< ') -# response = analyticsFrontend_client.StartAnalyzer(create_analyzer()) -# LOGGER.debug(str(response)) -# assert isinstance(response, AnalyzerId) - # To test start and stop listener together def test_StartAnalyzers(analyticsFrontend_client): LOGGER.info(' >>> test_StartAnalyzers START: <<< ') added_analyzer_id = analyticsFrontend_client.StartAnalyzer(create_analyzer()) LOGGER.debug(str(added_analyzer_id)) - LOGGER.info(' --> Calling StartResponseListener... ') - class_obj = AnalyticsFrontendServiceServicerImpl() - response = class_obj.StartResponseListener(added_analyzer_id.analyzer_id.uuid) - LOGGER.debug(response) - LOGGER.info("waiting for timer to comlete ...") - time.sleep(3) + # LOGGER.info(' --> Calling StartResponseListener... ') + # class_obj = AnalyticsFrontendServiceServicerImpl() + # response = class_obj.StartResponseListener(added_analyzer_id.analyzer_id.uuid) + # LOGGER.debug(response) + LOGGER.info("waiting for timer to complete ...") + time.sleep(15) LOGGER.info('--> StopAnalyzer') response = analyticsFrontend_client.StopAnalyzer(added_analyzer_id) LOGGER.debug(str(response)) diff --git a/src/common/tools/context_queries/Slice.py b/src/common/tools/context_queries/Slice.py index c826c59ce79adbe1e399d674ffd46a421ddd9b5e..686f08a845df5c99fdd0cace393290419d92922c 100644 --- a/src/common/tools/context_queries/Slice.py +++ b/src/common/tools/context_queries/Slice.py @@ -12,12 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc, logging -from typing import Optional +import logging +from typing import Optional, Tuple, Union +from uuid import UUID, uuid5 + +import grpc + from common.Constants import DEFAULT_CONTEXT_NAME -from common.proto.context_pb2 import Slice, SliceFilter, SliceId +from common.method_wrappers.ServiceExceptions import InvalidArgumentsException +from common.proto.context_pb2 import ContextId, Slice, SliceFilter, SliceId from context.client.ContextClient import ContextClient + +NAMESPACE_TFS = UUID("200e3a1f-2223-534f-a100-758e29c37f40") + LOGGER = logging.getLogger(__name__) def get_slice_by_id( @@ -59,3 +67,96 @@ def get_slice_by_uuid( context_client, slice_id, rw_copy=rw_copy, include_endpoint_ids=include_endpoint_ids, include_constraints=include_constraints, include_service_ids=include_service_ids, include_subslice_ids=include_subslice_ids, include_config_rules=include_config_rules) + + +def get_uuid_from_string( + str_uuid_or_name: Union[str, UUID], prefix_for_name: Optional[str] = None +) -> str: + # if UUID given, assume it is already a valid UUID + if isinstance(str_uuid_or_name, UUID): + return str_uuid_or_name + if not isinstance(str_uuid_or_name, str): + MSG = "Parameter({:s}) cannot be used to produce a UUID" + raise Exception(MSG.format(str(repr(str_uuid_or_name)))) + try: + # try to parse as UUID + return str(UUID(str_uuid_or_name)) + except: # pylint: disable=bare-except + # produce a UUID within TFS namespace from parameter + if prefix_for_name is not None: + str_uuid_or_name = "{:s}/{:s}".format(prefix_for_name, str_uuid_or_name) + return str(uuid5(NAMESPACE_TFS, str_uuid_or_name)) + + +def context_get_uuid( + context_id: ContextId, + context_name: str = "", + allow_random: bool = False, + allow_default: bool = False, +) -> str: + context_uuid = context_id.context_uuid.uuid + + if len(context_uuid) > 0: + return get_uuid_from_string(context_uuid) + if len(context_name) > 0: + return get_uuid_from_string(context_name) + if allow_default: + return get_uuid_from_string(DEFAULT_CONTEXT_NAME) + + raise InvalidArgumentsException( + [ + ("context_id.context_uuid.uuid", context_uuid), + ("name", context_name), + ], + extra_details=["At least one is required to produce a Context UUID"], + ) + + +def slice_get_uuid(slice_id: SliceId) -> Tuple[str, str]: + context_uuid = context_get_uuid(slice_id.context_id, allow_random=False) + raw_slice_uuid = slice_id.slice_uuid.uuid + + if len(raw_slice_uuid) > 0: + return context_uuid, get_uuid_from_string( + raw_slice_uuid, prefix_for_name=context_uuid + ) + + raise InvalidArgumentsException( + [ + ("slice_id.slice_uuid.uuid", raw_slice_uuid), + ], + extra_details=["At least one is required to produce a Slice UUID"], + ) + +def get_slice_by_defualt_id( + context_client : ContextClient, default_slice_id : SliceId, context_uuid : str = DEFAULT_CONTEXT_NAME, + rw_copy : bool = False, include_endpoint_ids : bool = True, include_constraints : bool = True, + include_service_ids : bool = True, include_subslice_ids : bool = True, include_config_rules : bool = True +) -> Optional[Slice]: + context_uuid, slice_uuid = slice_get_uuid(default_slice_id) + LOGGER.debug(f'P60: {context_uuid} {slice_uuid}') + slice_id = SliceId() + slice_id.context_id.context_uuid.uuid = context_uuid # pylint: disable=no-member + slice_id.slice_uuid.uuid = slice_uuid # pylint: disable=no-member + return get_slice_by_id( + context_client, slice_id, rw_copy=rw_copy, include_endpoint_ids=include_endpoint_ids, + include_constraints=include_constraints, include_service_ids=include_service_ids, + include_subslice_ids=include_subslice_ids, include_config_rules=include_config_rules) + + +def get_slice_by_defualt_name( + context_client : ContextClient, slice_name : str, context_uuid : str = DEFAULT_CONTEXT_NAME, + rw_copy : bool = False, include_endpoint_ids : bool = True, include_constraints : bool = True, + include_service_ids : bool = True, include_subslice_ids : bool = True, include_config_rules : bool = True +) -> Optional[Slice]: + default_slice_id = SliceId() + default_slice_id.context_id.context_uuid.uuid = context_uuid # pylint: disable=no-member + default_slice_id.slice_uuid.uuid = slice_name # pylint: disable=no-member + context_uuid, slice_uuid = slice_get_uuid(default_slice_id) + slice_id = SliceId() + slice_id.context_id.context_uuid.uuid = context_uuid # pylint: disable=no-member + slice_id.slice_uuid.uuid = slice_uuid # pylint: disable=no-member + return get_slice_by_id( + context_client, slice_id, rw_copy=rw_copy, include_endpoint_ids=include_endpoint_ids, + include_constraints=include_constraints, include_service_ids=include_service_ids, + include_subslice_ids=include_subslice_ids, include_config_rules=include_config_rules) diff --git a/src/common/tools/kafka/Variables.py b/src/common/tools/kafka/Variables.py index 21e66af137302553663d6e0a6701368bda638017..5c7501b6c07e6aaa26569e2817fca374e6b0c12e 100644 --- a/src/common/tools/kafka/Variables.py +++ b/src/common/tools/kafka/Variables.py @@ -41,14 +41,14 @@ class KafkaConfig(Enum): class KafkaTopic(Enum): # TODO: Later to be populated from ENV variable. - REQUEST = 'topic_request' - RESPONSE = 'topic_response' + TELEMETRY_REQUEST = 'topic_telemetry_request' + TELEMETRY_RESPONSE = 'topic_telemetry_response' RAW = 'topic_raw' LABELED = 'topic_labeled' VALUE = 'topic_value' ALARMS = 'topic_alarms' - ANALYTICS_REQUEST = 'topic_request_analytics' - ANALYTICS_RESPONSE = 'topic_response_analytics' + ANALYTICS_REQUEST = 'topic_analytics_request' + ANALYTICS_RESPONSE = 'topic_analytics_response' @staticmethod def create_all_topics() -> bool: diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py index b99ee50ca8319ab96f9062a3c58c356fa2ae7ec7..2e53cfa03ed4d3c5b4883edb939f6add79416f3f 100644 --- a/src/device/service/drivers/__init__.py +++ b/src/device/service/drivers/__init__.py @@ -81,6 +81,16 @@ DRIVERS.append( } ])) + +from .ietf_l3vpn.driver import IetfL3VpnDriver # pylint: disable=wrong-import-position +DRIVERS.append( + (IetfL3VpnDriver, [ + { + FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.TERAFLOWSDN_CONTROLLER, + FilterFieldEnum.DRIVER: DeviceDriverEnum.DEVICEDRIVER_IETF_L3VPN, + } + ])) + from .ietf_actn.IetfActnDriver import IetfActnDriver # pylint: disable=wrong-import-position DRIVERS.append( (IetfActnDriver, [ diff --git a/src/device/service/drivers/emulated/Tools.py b/src/device/service/drivers/emulated/Tools.py index 9f2a105c0d9735f486f41fab5bc3069ec9327f65..2a6f9c95d351f447f7368584d0ad07889eaecd7c 100644 --- a/src/device/service/drivers/emulated/Tools.py +++ b/src/device/service/drivers/emulated/Tools.py @@ -82,6 +82,26 @@ def compose_resource_endpoint(endpoint_data : Dict[str, Any]) -> Optional[Tuple[ 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 diff --git a/src/device/service/drivers/ietf_l3vpn/Constants.py b/src/device/service/drivers/ietf_l3vpn/Constants.py new file mode 100644 index 0000000000000000000000000000000000000000..df66eb16b3d78c1b388a086011ed6f6b75b8099f --- /dev/null +++ b/src/device/service/drivers/ietf_l3vpn/Constants.py @@ -0,0 +1,25 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from 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/ietf_l3vpn/TfsApiClient.py b/src/device/service/drivers/ietf_l3vpn/TfsApiClient.py new file mode 100644 index 0000000000000000000000000000000000000000..1ca965f8777aa23287ad379c8ac2cd0d92d9c28f --- /dev/null +++ b/src/device/service/drivers/ietf_l3vpn/TfsApiClient.py @@ -0,0 +1,187 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import Dict, List, Optional + +import requests +from requests.auth import HTTPBasicAuth + +from device.service.driver_api.ImportTopologyEnum import ImportTopologyEnum + +GET_DEVICES_URL = "{:s}://{:s}:{:d}/tfs-api/devices" +GET_LINKS_URL = "{:s}://{:s}:{:d}/tfs-api/links" +L3VPN_URL = "{:s}://{:s}:{:d}/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services" +TIMEOUT = 30 + +HTTP_OK_CODES = { + 200, # OK + 201, # Created + 202, # Accepted + 204, # No Content +} + +MAPPING_STATUS = { + "DEVICEOPERATIONALSTATUS_UNDEFINED": 0, + "DEVICEOPERATIONALSTATUS_DISABLED": 1, + "DEVICEOPERATIONALSTATUS_ENABLED": 2, +} + +MAPPING_DRIVER = { + "DEVICEDRIVER_UNDEFINED": 0, + "DEVICEDRIVER_OPENCONFIG": 1, + "DEVICEDRIVER_TRANSPORT_API": 2, + "DEVICEDRIVER_P4": 3, + "DEVICEDRIVER_IETF_NETWORK_TOPOLOGY": 4, + "DEVICEDRIVER_ONF_TR_532": 5, + "DEVICEDRIVER_XR": 6, + "DEVICEDRIVER_IETF_L2VPN": 7, + "DEVICEDRIVER_GNMI_OPENCONFIG": 8, + "DEVICEDRIVER_OPTICAL_TFS": 9, + "DEVICEDRIVER_IETF_ACTN": 10, + "DEVICEDRIVER_OC": 11, +} + +MSG_ERROR = "Could not retrieve devices in remote TeraFlowSDN instance({:s}). status_code={:s} reply={:s}" + +LOGGER = logging.getLogger(__name__) + + +class TfsApiClient: + def __init__( + self, + address: str, + port: int, + scheme: str = "http", + username: Optional[str] = None, + password: Optional[str] = None, + ) -> None: + self._devices_url = GET_DEVICES_URL.format(scheme, address, port) + self._links_url = GET_LINKS_URL.format(scheme, address, port) + self._l3vpn_url = L3VPN_URL.format(scheme, address, port) + self._auth = None + # ( + # HTTPBasicAuth(username, password) + # if username is not None and password is not None + # else None + # ) + + def get_devices_endpoints( + self, import_topology: ImportTopologyEnum = ImportTopologyEnum.DEVICES + ) -> List[Dict]: + LOGGER.debug("[get_devices_endpoints] begin") + LOGGER.debug( + "[get_devices_endpoints] import_topology={:s}".format(str(import_topology)) + ) + + reply = requests.get(self._devices_url, timeout=TIMEOUT, auth=self._auth) + if reply.status_code not in HTTP_OK_CODES: + msg = MSG_ERROR.format( + str(self._devices_url), str(reply.status_code), str(reply) + ) + LOGGER.error(msg) + raise Exception(msg) + + if import_topology == ImportTopologyEnum.DISABLED: + raise Exception( + "Unsupported import_topology mode: {:s}".format(str(import_topology)) + ) + + result = list() + for json_device in reply.json()["devices"]: + device_uuid: str = json_device["device_id"]["device_uuid"]["uuid"] + device_type: str = json_device["device_type"] + device_status = json_device["device_operational_status"] + device_url = "/devices/device[{:s}]".format(device_uuid) + device_data = { + "uuid": json_device["device_id"]["device_uuid"]["uuid"], + "name": json_device["name"], + "type": device_type, + "status": MAPPING_STATUS[device_status], + "drivers": [ + MAPPING_DRIVER[driver] for driver in json_device["device_drivers"] + ], + } + result.append((device_url, device_data)) + + for json_endpoint in json_device["device_endpoints"]: + endpoint_uuid = json_endpoint["endpoint_id"]["endpoint_uuid"]["uuid"] + endpoint_url = "/endpoints/endpoint[{:s}]".format(endpoint_uuid) + endpoint_data = { + "device_uuid": device_uuid, + "uuid": endpoint_uuid, + "name": json_endpoint["name"], + "type": json_endpoint["endpoint_type"], + } + result.append((endpoint_url, endpoint_data)) + + if import_topology == ImportTopologyEnum.DEVICES: + LOGGER.debug("[get_devices_endpoints] devices only; returning") + return result + + reply = requests.get(self._links_url, timeout=TIMEOUT, auth=self._auth) + if reply.status_code not in HTTP_OK_CODES: + msg = MSG_ERROR.format( + str(self._links_url), str(reply.status_code), str(reply) + ) + LOGGER.error(msg) + raise Exception(msg) + + for json_link in reply.json()["links"]: + link_uuid: str = json_link["link_id"]["link_uuid"]["uuid"] + link_url = "/links/link[{:s}]".format(link_uuid) + link_endpoint_ids = [ + ( + json_endpoint_id["device_id"]["device_uuid"]["uuid"], + json_endpoint_id["endpoint_uuid"]["uuid"], + ) + for json_endpoint_id in json_link["link_endpoint_ids"] + ] + link_data = { + "uuid": json_link["link_id"]["link_uuid"]["uuid"], + "name": json_link["name"], + "endpoints": link_endpoint_ids, + } + result.append((link_url, link_data)) + + LOGGER.debug("[get_devices_endpoints] topology; returning") + return result + + def create_connectivity_service(self, l3vpn_data: dict) -> None: + try: + requests.post(self._l3vpn_url, json=l3vpn_data) + LOGGER.debug( + "[create_connectivity_service] l3vpn_data={:s}".format(str(l3vpn_data)) + ) + except requests.exceptions.ConnectionError: + raise Exception("faild to send post request to TFS L3VPN NBI") + + def update_connectivity_service(self, l3vpn_data: dict) -> None: + vpn_id = l3vpn_data['ietf-l3vpn-svc:l3vpn-svc']["vpn-services"]["vpn-service"][0]["vpn-id"] + url = self._l3vpn_url + f"/vpn-service={vpn_id}" + try: + requests.put(url, json=l3vpn_data) + LOGGER.debug( + "[update_connectivity_service] l3vpn_data={:s}".format(str(l3vpn_data)) + ) + except requests.exceptions.ConnectionError: + raise Exception("faild to send post request to TFS L3VPN NBI") + + def delete_connectivity_service(self, service_uuid: str) -> None: + url = self._l3vpn_url + f"/vpn-service={service_uuid}" + try: + requests.delete(url, auth=self._auth) + LOGGER.debug("[delete_connectivity_service] url={:s}".format(str(url))) + except requests.exceptions.ConnectionError: + raise Exception("faild to send delete request to TFS L3VPN NBI") diff --git a/src/device/service/drivers/ietf_l3vpn/Tools.py b/src/device/service/drivers/ietf_l3vpn/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..7caaa27a52cacf03d9822e933f0a6b74fd0e4db8 --- /dev/null +++ b/src/device/service/drivers/ietf_l3vpn/Tools.py @@ -0,0 +1,198 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +from typing import Any, Dict, Optional, Tuple, TypedDict + +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 + + +class LANPrefixesDict(TypedDict): + lan: str + lan_tag: str + + +LOGGER = logging.getLogger(__name__) + +SITE_NETWORK_ACCESS_TYPE = "ietf-l3vpn-svc:multipoint" + + +def service_exists(wim_url: str, auth, service_uuid: str) -> bool: + try: + get_connectivity_service(wim_url, auth, service_uuid) + return True + except: # pylint: disable=bare-except + return False + + +def get_all_active_connectivity_services(wim_url: str, auth): + try: + LOGGER.info("Sending get all connectivity services") + servicepoint = f"{wim_url}/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services" + response = requests.get(servicepoint, auth=auth) + + if response.status_code != requests.codes.ok: + raise Exception( + "Unable to get all connectivity services", + http_code=response.status_code, + ) + + return response + except requests.exceptions.ConnectionError: + raise Exception("Request Timeout", http_code=408) + + +def get_connectivity_service(wim_url, auth, service_uuid): + try: + LOGGER.info("Sending get connectivity service") + servicepoint = f"{wim_url}/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service={service_uuid}" + + response = requests.get(servicepoint) + + if response.status_code != requests.codes.ok: + raise Exception( + "Unable to get connectivity service{:s}".format(str(service_uuid)), + http_code=response.status_code, + ) + + return response + except requests.exceptions.ConnectionError: + raise Exception("Request Timeout", http_code=408) + + +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, "site_location", endpoint_resource_value + ) + process_optional_string_field(endpoint_data, "ce-ip", endpoint_resource_value) + process_optional_string_field( + endpoint_data, "address_ip", endpoint_resource_value + ) + process_optional_string_field( + endpoint_data, "address_prefix", endpoint_resource_value + ) + process_optional_string_field(endpoint_data, "mtu", endpoint_resource_value) + process_optional_string_field( + endpoint_data, "ipv4_lan_prefixes", 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 "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 diff --git a/src/device/service/drivers/ietf_l3vpn/__init__.py b/src/device/service/drivers/ietf_l3vpn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bbfc943b68af13a11e562abbc8680ade71db8f02 --- /dev/null +++ b/src/device/service/drivers/ietf_l3vpn/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/device/service/drivers/ietf_l3vpn/driver.py b/src/device/service/drivers/ietf_l3vpn/driver.py new file mode 100644 index 0000000000000000000000000000000000000000..2aca83b6a645bf2e793b08841949813f0413a531 --- /dev/null +++ b/src/device/service/drivers/ietf_l3vpn/driver.py @@ -0,0 +1,309 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging +import re +import threading +from typing import Any, Iterator, List, Optional, Tuple, Union + +import anytree +import requests +from requests.auth import HTTPBasicAuth + +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.type_checkers.Checkers import chk_length, chk_string, chk_type +from device.service.driver_api._Driver import ( + RESOURCE_ENDPOINTS, + RESOURCE_SERVICES, + _Driver, +) +from device.service.driver_api.AnyTreeTools import ( + TreeNode, + dump_subtree, + get_subnode, + set_subnode_value, +) +from device.service.driver_api.ImportTopologyEnum import ( + ImportTopologyEnum, + get_import_topology, +) + +from .Constants import SPECIAL_RESOURCE_MAPPINGS +from .TfsApiClient import TfsApiClient +from .Tools import compose_resource_endpoint + +LOGGER = logging.getLogger(__name__) + + +ALL_RESOURCE_KEYS = [ + RESOURCE_ENDPOINTS, + RESOURCE_SERVICES, +] + +RE_GET_ENDPOINT_FROM_INTERFACE = re.compile(r"^\/interface\[([^\]]+)\].*") + +RE_IETF_L3VPN_DATA = re.compile(r"^\/service\[[^\]]+\]\/IETFL3VPN$") +RE_IETF_L3VPN_OPERATION = re.compile(r"^\/service\[[^\]]+\]\/IETFL3VPN\/operation$") + +DRIVER_NAME = "ietf_l3vpn" +METRICS_POOL = MetricsPool("Device", "Driver", labels={"driver": DRIVER_NAME}) + + +class IetfL3VpnDriver(_Driver): + def __init__(self, address: str, port: str, **settings) -> None: + super().__init__(DRIVER_NAME, address, int(port), **settings) + self.__lock = threading.Lock() + self.__started = threading.Event() + self.__terminate = threading.Event() + self.__running = TreeNode(".") + scheme = self.settings.get("scheme", "http") + username = self.settings.get("username") + password = self.settings.get("password") + self.tac = TfsApiClient( + self.address, + self.port, + scheme=scheme, + username=username, + password=password, + ) + self.__auth = None + # ( + # HTTPBasicAuth(username, password) + # if username is not None and password is not None + # else None + # ) + self.__tfs_nbi_root = "{:s}://{:s}:{:d}".format( + scheme, self.address, int(self.port) + ) + self.__timeout = int(self.settings.get("timeout", 120)) + self.__import_topology = get_import_topology( + self.settings, default=ImportTopologyEnum.DEVICES + ) + 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._set_initial_config(endpoint_resources) + + def _set_initial_config( + 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_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 + + set_subnode_value( + resolver, self.__running, resource_path, resource_value + ) + + results.append(True) + return results + + def Connect(self) -> bool: + url = ( + self.__tfs_nbi_root + "/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services" + ) + with self.__lock: + if self.__started.is_set(): + return True + try: + # requests.get(url, timeout=self.__timeout, auth=self.__auth) + ... + except requests.exceptions.Timeout: + LOGGER.exception("Timeout connecting {:s}".format(url)) + return False + except Exception: # pylint: disable=broad-except + LOGGER.exception("Exception connecting {:s}".format(url)) + return False + else: + self.__started.set() + return True + + def Disconnect(self) -> bool: + with self.__lock: + self.__terminate.set() + return True + + @metered_subclass_method(METRICS_POOL) + def GetInitialConfig(self) -> List[Tuple[str, Any]]: + with self.__lock: + return [] + + @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 + return results + + @metered_subclass_method(METRICS_POOL) + def SetConfig( + self, resources: List[Tuple[str, Any]] + ) -> List[Union[bool, Exception]]: + results = [] + if len(resources) == 0: + return results + with self.__lock: + for resource in resources: + resource_key, resource_value = resource + if RE_IETF_L3VPN_OPERATION.match(resource_key): + operation_type = json.loads(resource_value)["type"] + results.append((resource_key, True)) + break + else: + raise Exception("operation type not found in resources") + for resource in resources: + LOGGER.info("resource = {:s}".format(str(resource))) + resource_key, resource_value = resource + if not RE_IETF_L3VPN_DATA.match(resource_key): + continue + try: + resource_value = json.loads(resource_value) + + # if service_exists(self.__tfs_nbi_root, self.__auth, service_uuid): + # exc = NotImplementedError( + # "IETF L3VPN Service Update is still not supported" + # ) + # results.append((resource[0], exc)) + # continue + if operation_type == "create": + service_id = resource_value["ietf-l3vpn-svc:l3vpn-svc"][ + "vpn-services" + ]["vpn-service"][0]["vpn-id"] + self.tac.create_connectivity_service(resource_value) + elif operation_type == "update": + service_id = resource_value["ietf-l3vpn-svc:l3vpn-svc"][ + "vpn-services" + ]["vpn-service"][0]["vpn-id"] + self.tac.update_connectivity_service(resource_value) + else: + raise Exception("operation type not supported") + results.append((resource_key, True)) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception( + "Unhandled error processing resource_key({:s})".format( + str(resource_key) + ) + ) + results.append((resource_key, e)) + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig( + self, resources: List[Tuple[str, Any]] + ) -> List[Union[bool, Exception]]: + results = [] + if len(resources) == 0: + return results + with self.__lock: + for resource in resources: + LOGGER.info("resource = {:s}".format(str(resource))) + resource_key, resource_value = resource + if not RE_IETF_L3VPN_DATA.match(resource_key): + continue + try: + resource_value = json.loads(resource_value) + service_id = resource_value["id"] + + # if service_exists(self.__tfs_nbi_root, self.__auth, service_uuid): + self.tac.delete_connectivity_service(service_id) + results.append((resource_key, True)) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception( + "Unhandled error processing resource_key({:s})".format( + str(resource_key) + ) + ) + results.append((resource_key, e)) + return results + + @metered_subclass_method(METRICS_POOL) + def SubscribeState( + self, subscriptions: List[Tuple[str, float, float]] + ) -> List[Union[bool, Exception]]: + # TODO: IETF L3VPN does not support monitoring by now + return [False for _ in subscriptions] + + @metered_subclass_method(METRICS_POOL) + def UnsubscribeState( + self, subscriptions: List[Tuple[str, float, float]] + ) -> List[Union[bool, Exception]]: + # TODO: IETF L3VPN does not support monitoring by now + return [False for _ in subscriptions] + + def GetState( + self, blocking=False, terminate: Optional[threading.Event] = None + ) -> Iterator[Tuple[float, str, Any]]: + # TODO: IETF L3VPN does not support monitoring by now + return [] diff --git a/src/device/service/drivers/ietf_slice/Constants.py b/src/device/service/drivers/ietf_slice/Constants.py new file mode 100644 index 0000000000000000000000000000000000000000..172c328aeaa5a2beafe4ced1b273cb3e7577e241 --- /dev/null +++ b/src/device/service/drivers/ietf_slice/Constants.py @@ -0,0 +1,25 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from 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/ietf_slice/Tools.py b/src/device/service/drivers/ietf_slice/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..bd976927e4b17c74264b3372ed1caeeb1bc96c61 --- /dev/null +++ b/src/device/service/drivers/ietf_slice/Tools.py @@ -0,0 +1,147 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +from typing import Any, Dict, Optional, Tuple + +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 + +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, "site_location", endpoint_resource_value + ) + process_optional_string_field(endpoint_data, "ce-ip", endpoint_resource_value) + process_optional_string_field( + endpoint_data, "address_ip", endpoint_resource_value + ) + process_optional_string_field( + endpoint_data, "address_prefix", endpoint_resource_value + ) + process_optional_string_field(endpoint_data, "mtu", endpoint_resource_value) + process_optional_string_field( + endpoint_data, "ipv4_lan_prefixes", 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 "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 diff --git a/src/device/service/drivers/ietf_slice/__init__.py b/src/device/service/drivers/ietf_slice/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6242c89c7fa17bc5b6cc44328d8ce58438721d45 --- /dev/null +++ b/src/device/service/drivers/ietf_slice/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/device/service/drivers/ietf_slice/driver.py b/src/device/service/drivers/ietf_slice/driver.py new file mode 100644 index 0000000000000000000000000000000000000000..e8b6e7d0effac1f7a3eb05c7aabd2d0a39125b65 --- /dev/null +++ b/src/device/service/drivers/ietf_slice/driver.py @@ -0,0 +1,306 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging +import re +import threading +from typing import Any, Iterator, List, Optional, Tuple, Union + +import anytree +import requests +from requests.auth import HTTPBasicAuth + +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.type_checkers.Checkers import chk_length, chk_string, chk_type +from device.service.driver_api._Driver import ( + RESOURCE_ENDPOINTS, + RESOURCE_SERVICES, + _Driver, +) +from device.service.driver_api.AnyTreeTools import ( + TreeNode, + dump_subtree, + get_subnode, + set_subnode_value, +) +from device.service.driver_api.ImportTopologyEnum import ( + ImportTopologyEnum, + get_import_topology, +) + +from .Constants import SPECIAL_RESOURCE_MAPPINGS +from .tfs_slice_nbi_client import TfsApiClient +from .Tools import compose_resource_endpoint + +LOGGER = logging.getLogger(__name__) + + +ALL_RESOURCE_KEYS = [ + RESOURCE_ENDPOINTS, + RESOURCE_SERVICES, +] + +RE_IETF_SLICE_DATA = re.compile(r"^\/service\[[^\]]+\]\/IETFSlice$") +RE_IETF_SLICE_OPERATION = re.compile(r"^\/service\[[^\]]+\]\/IETFSlice\/operation$") + +DRIVER_NAME = "ietf_slice" +METRICS_POOL = MetricsPool("Device", "Driver", labels={"driver": DRIVER_NAME}) + + +class IetfSliceDriver(_Driver): + def __init__(self, address: str, port: str, **settings) -> None: + super().__init__(DRIVER_NAME, address, int(port), **settings) + self.__lock = threading.Lock() + self.__started = threading.Event() + self.__terminate = threading.Event() + self.__running = TreeNode(".") + scheme = self.settings.get("scheme", "http") + username = self.settings.get("username") + password = self.settings.get("password") + self.tac = TfsApiClient( + self.address, + self.port, + scheme=scheme, + username=username, + password=password, + ) + self.__auth = None + # ( + # HTTPBasicAuth(username, password) + # if username is not None and password is not None + # else None + # ) + self.__tfs_nbi_root = "{:s}://{:s}:{:d}".format( + scheme, self.address, int(self.port) + ) + self.__timeout = int(self.settings.get("timeout", 120)) + self.__import_topology = get_import_topology( + self.settings, default=ImportTopologyEnum.DEVICES + ) + 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._set_initial_config(endpoint_resources) + + def _set_initial_config( + 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_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 + + set_subnode_value( + resolver, self.__running, resource_path, resource_value + ) + + results.append(True) + return results + + def Connect(self) -> bool: + url = self.__tfs_nbi_root + "/restconf/data/ietf-network-slice-service:ietf-nss" + with self.__lock: + if self.__started.is_set(): + return True + try: + # requests.get(url, timeout=self.__timeout) + ... + except requests.exceptions.Timeout: + LOGGER.exception("Timeout connecting {:s}".format(url)) + return False + except Exception: # pylint: disable=broad-except + LOGGER.exception("Exception connecting {:s}".format(url)) + return False + else: + self.__started.set() + return True + + def Disconnect(self) -> bool: + with self.__lock: + self.__terminate.set() + return True + + @metered_subclass_method(METRICS_POOL) + def GetInitialConfig(self) -> List[Tuple[str, Any]]: + with self.__lock: + return [] + + @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]]: + results = [] + + if len(resources) == 0: + return results + + with self.__lock: + for resource in resources: + resource_key, resource_value = resource + if RE_IETF_SLICE_OPERATION.match(resource_key): + operation_type = json.loads(resource_value)["type"] + results.append((resource_key, True)) + break + else: + raise Exception("operation type not found in resources") + for resource in resources: + LOGGER.info("resource = {:s}".format(str(resource))) + resource_key, resource_value = resource + if not RE_IETF_SLICE_DATA.match(resource_key): + continue + try: + resource_value = json.loads(resource_value) + + slice_name = resource_value["network-slice-services"][ + "slice-service" + ][0]["id"] + + if operation_type == "create": + self.tac.create_slice(resource_value) + + elif operation_type == "update": + connection_groups = resource_value["network-slice-services"][ + "slice-service" + ][0]["connection-groups"]["connection-group"] + + if len(connection_groups) != 1: + raise Exception("only one connection group is supported") + + connection_group = connection_groups[0] + + self.tac.update_slice( + slice_name, connection_group["id"], connection_group + ) + + elif operation_type == "delete": + self.tac.delete_slice(slice_name) + + results.append((resource_key, True)) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception( + "Unhandled error processing resource_key({:s})".format( + str(resource_key) + ) + ) + results.append((resource_key, e)) + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig( + self, resources: List[Tuple[str, Any]] + ) -> List[Union[bool, Exception]]: + results = [] + + if len(resources) == 0: + return results + + with self.__lock: + for resource in resources: + LOGGER.info("resource = {:s}".format(str(resource))) + resource_key, resource_value = resource + try: + results.append((resource_key, True)) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception( + "Unhandled error processing resource_key({:s})".format( + str(resource_key) + ) + ) + results.append((resource_key, e)) + return results + + @metered_subclass_method(METRICS_POOL) + def SubscribeState( + self, subscriptions: List[Tuple[str, float, float]] + ) -> List[Union[bool, Exception]]: + # TODO: IETF Slice does not support monitoring by now + return [False for _ in subscriptions] + + @metered_subclass_method(METRICS_POOL) + def UnsubscribeState( + self, subscriptions: List[Tuple[str, float, float]] + ) -> List[Union[bool, Exception]]: + # TODO: IETF Slice does not support monitoring by now + return [False for _ in subscriptions] + + def GetState( + self, blocking=False, terminate: Optional[threading.Event] = None + ) -> Iterator[Tuple[float, str, Any]]: + # TODO: IETF Slice does not support monitoring by now + return [] diff --git a/src/device/service/drivers/ietf_slice/tfs_slice_nbi_client.py b/src/device/service/drivers/ietf_slice/tfs_slice_nbi_client.py new file mode 100644 index 0000000000000000000000000000000000000000..596e3d903d6e17dda96be42d776aae2f9068f7bd --- /dev/null +++ b/src/device/service/drivers/ietf_slice/tfs_slice_nbi_client.py @@ -0,0 +1,76 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import Optional + +import requests +from requests.auth import HTTPBasicAuth + +IETF_SLICE_URL = "{:s}://{:s}:{:d}/restconf/data/ietf-network-slice-service" +TIMEOUT = 30 + +LOGGER = logging.getLogger(__name__) + +HEADERS = {"Content-Type": "application/json"} + + +class TfsApiClient: + def __init__( + self, + address: str, + port: int, + scheme: str = "http", + username: Optional[str] = None, + password: Optional[str] = None, + ) -> None: + self._slice_url = IETF_SLICE_URL.format(scheme, address, port) + self._auth = None + # ( + # HTTPBasicAuth(username, password) + # if username is not None and password is not None + # else None + # ) + + def create_slice(self, slice_data: dict) -> None: + url = self._slice_url + ":network-slice-services" + try: + requests.post(url, json=slice_data, headers=HEADERS) + LOGGER.info(f"IETF Slice Post to {url}: {slice_data}") + except requests.exceptions.ConnectionError: + raise Exception("faild to send post request to TFS IETF Slice NBI") + + def update_slice( + self, + slice_name: str, + connection_group_id: str, + updated_connection_group_data: dict, + ) -> None: + url = ( + self._slice_url + + f":network-slice-services/slice-service={slice_name}/connection-groups/connection-group={connection_group_id}" + ) + try: + requests.put(url, json=updated_connection_group_data, headers=HEADERS) + LOGGER.info(f"IETF Slice Put to {url}: {updated_connection_group_data}") + except requests.exceptions.ConnectionError: + raise Exception("faild to send update request to TFS IETF Slice NBI") + + def delete_slice(self, slice_name: str) -> None: + url = self._slice_url + f":network-slice-services/slice-service={slice_name}" + try: + requests.delete(url) + LOGGER.info(f"IETF Slice Delete to {url}") + except requests.exceptions.ConnectionError: + raise Exception("faild to send delete request to TFS IETF Slice NBI") diff --git a/src/device/service/drivers/nce/Constants.py b/src/device/service/drivers/nce/Constants.py new file mode 100644 index 0000000000000000000000000000000000000000..172c328aeaa5a2beafe4ced1b273cb3e7577e241 --- /dev/null +++ b/src/device/service/drivers/nce/Constants.py @@ -0,0 +1,25 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from 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/nce/Tools.py b/src/device/service/drivers/nce/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..f9b2f24a8df494d04d749624ea6b2e5b986944fc --- /dev/null +++ b/src/device/service/drivers/nce/Tools.py @@ -0,0 +1,145 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +from typing import Any, Dict, Optional, Tuple + +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 + +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, "site_location", endpoint_resource_value + ) + process_optional_string_field(endpoint_data, "ce-ip", endpoint_resource_value) + process_optional_string_field( + endpoint_data, "address_ip", endpoint_resource_value + ) + process_optional_string_field( + endpoint_data, "address_prefix", endpoint_resource_value + ) + process_optional_string_field(endpoint_data, "mtu", endpoint_resource_value) + process_optional_string_field( + endpoint_data, "ipv4_lan_prefixes", 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 "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 diff --git a/src/device/service/drivers/nce/__init__.py b/src/device/service/drivers/nce/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6242c89c7fa17bc5b6cc44328d8ce58438721d45 --- /dev/null +++ b/src/device/service/drivers/nce/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/device/service/drivers/nce/driver.py b/src/device/service/drivers/nce/driver.py new file mode 100644 index 0000000000000000000000000000000000000000..4ac1a2b1c604a6ebe5028688dc39f545e0e1cee6 --- /dev/null +++ b/src/device/service/drivers/nce/driver.py @@ -0,0 +1,278 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging +import re +import threading +from typing import Any, Iterator, List, Optional, Tuple, Union + +import anytree +import requests +from requests.auth import HTTPBasicAuth + +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.type_checkers.Checkers import 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 device.service.driver_api.ImportTopologyEnum import ( + ImportTopologyEnum, + get_import_topology, +) + +from .Constants import SPECIAL_RESOURCE_MAPPINGS +from .nce_fan_client import NCEClient +from .Tools import compose_resource_endpoint + +LOGGER = logging.getLogger(__name__) + + +RE_NCE_APP_FLOW_DATA = re.compile(r"^\/service\[[^\]]+\]\/AppFlow$") +RE_NCE_APP_FLOW_OPERATION = re.compile(r"^\/service\[[^\]]+\]\/AppFlow\/operation$") + +DRIVER_NAME = "nce" +METRICS_POOL = MetricsPool("Device", "Driver", labels={"driver": DRIVER_NAME}) + + +class NCEDriver(_Driver): + def __init__(self, address: str, port: str, **settings) -> None: + super().__init__(DRIVER_NAME, address, int(port), **settings) + self.__lock = threading.Lock() + self.__started = threading.Event() + self.__terminate = threading.Event() + self.__running = TreeNode(".") + scheme = self.settings.get("scheme", "http") + username = self.settings.get("username") + password = self.settings.get("password") + self.nce = NCEClient( + self.address, + self.port, + scheme=scheme, + username=username, + password=password, + ) + self.__auth = None + # ( + # HTTPBasicAuth(username, password) + # if username is not None and password is not None + # else None + # ) + self.__tfs_nbi_root = "{:s}://{:s}:{:d}".format( + scheme, self.address, int(self.port) + ) + self.__timeout = int(self.settings.get("timeout", 120)) + self.__import_topology = get_import_topology( + self.settings, default=ImportTopologyEnum.DEVICES + ) + 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._set_initial_config(endpoint_resources) + + def _set_initial_config( + 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_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 + + set_subnode_value( + resolver, self.__running, resource_path, resource_value + ) + + results.append(True) + return results + + def Connect(self) -> bool: + with self.__lock: + if self.__started.is_set(): + return True + try: + ... + except requests.exceptions.Timeout: + return False + except Exception: # pylint: disable=broad-except + return False + else: + self.__started.set() + return True + + def Disconnect(self) -> bool: + with self.__lock: + self.__terminate.set() + return True + + @metered_subclass_method(METRICS_POOL) + def GetInitialConfig(self) -> List[Tuple[str, Any]]: + with self.__lock: + return [] + + @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 + return results + + @metered_subclass_method(METRICS_POOL) + def SetConfig( + self, resources: List[Tuple[str, Any]] + ) -> List[Union[bool, Exception]]: + results = [] + + if len(resources) == 0: + return results + + with self.__lock: + for resource in resources: + resource_key, resource_value = resource + LOGGER.debug("resource = {:s}".format(str(resource))) + if RE_NCE_APP_FLOW_OPERATION.match(resource_key): + operation_type = json.loads(resource_value)["type"] + results.append((resource_key, True)) + break + else: + raise Exception("operation type not found in resources") + for resource in resources: + LOGGER.info("resource = {:s}".format(str(resource))) + resource_key, resource_value = resource + if not RE_NCE_APP_FLOW_DATA.match(resource_key): + continue + try: + resource_value = json.loads(resource_value) + if operation_type == "create": + + self.nce.create_app_flow(resource_value) + elif operation_type == "delete": + + app_flow_name = resource_value["huawei-nce-app-flow:app-flows"][ + "app-flow" + ][0]["app-name"] + self.nce.delete_app_flow(app_flow_name) + results.append((resource_key, True)) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception( + "Unhandled error processing resource_key({:s})".format( + str(resource_key) + ) + ) + results.append((resource_key, e)) + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig( + self, resources: List[Tuple[str, Any]] + ) -> List[Union[bool, Exception]]: + results = [] + if len(resources) == 0: + return results + with self.__lock: + for resource in resources: + LOGGER.info("resource = {:s}".format(str(resource))) + resource_key, resource_value = resource + try: + results.append((resource_key, True)) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception( + "Unhandled error processing resource_key({:s})".format( + str(resource_key) + ) + ) + results.append((resource_key, e)) + return results + + @metered_subclass_method(METRICS_POOL) + def SubscribeState( + self, subscriptions: List[Tuple[str, float, float]] + ) -> List[Union[bool, Exception]]: + # TODO: IETF L3VPN does not support monitoring by now + return [False for _ in subscriptions] + + @metered_subclass_method(METRICS_POOL) + def UnsubscribeState( + self, subscriptions: List[Tuple[str, float, float]] + ) -> List[Union[bool, Exception]]: + # TODO: IETF L3VPN does not support monitoring by now + return [False for _ in subscriptions] + + def GetState( + self, blocking=False, terminate: Optional[threading.Event] = None + ) -> Iterator[Tuple[float, str, Any]]: + # TODO: IETF L3VPN does not support monitoring by now + return [] diff --git a/src/device/service/drivers/nce/nce_fan_client.py b/src/device/service/drivers/nce/nce_fan_client.py new file mode 100644 index 0000000000000000000000000000000000000000..1c3523427ab5818b650a5eeecafc098b1c55662e --- /dev/null +++ b/src/device/service/drivers/nce/nce_fan_client.py @@ -0,0 +1,94 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from typing import Optional + +import requests +from requests.auth import HTTPBasicAuth + +LOGGER = logging.getLogger(__name__) + +NCE_FAN_URL = "{:s}://{:s}:{:d}/restconf/v1/data" +TIMEOUT = 30 + +HTTP_OK_CODES = { + 200, # OK + 201, # Created + 202, # Accepted + 204, # No Content +} + +MAPPING_STATUS = { + "DEVICEOPERATIONALSTATUS_UNDEFINED": 0, + "DEVICEOPERATIONALSTATUS_DISABLED": 1, + "DEVICEOPERATIONALSTATUS_ENABLED": 2, +} + +MAPPING_DRIVER = { + "DEVICEDRIVER_UNDEFINED": 0, + "DEVICEDRIVER_OPENCONFIG": 1, + "DEVICEDRIVER_TRANSPORT_API": 2, + "DEVICEDRIVER_P4": 3, + "DEVICEDRIVER_IETF_NETWORK_TOPOLOGY": 4, + "DEVICEDRIVER_ONF_TR_532": 5, + "DEVICEDRIVER_XR": 6, + "DEVICEDRIVER_IETF_L2VPN": 7, + "DEVICEDRIVER_GNMI_OPENCONFIG": 8, + "DEVICEDRIVER_OPTICAL_TFS": 9, + "DEVICEDRIVER_IETF_ACTN": 10, + "DEVICEDRIVER_OC": 11, +} + +HEADERS = {'Content-Type': 'application/json'} + +class NCEClient: + def __init__( + self, + address: str, + port: int, + scheme: str = "http", + username: Optional[str] = None, + password: Optional[str] = None, + ) -> None: + self._nce_fan_url = NCE_FAN_URL.format(scheme, address, port) + self._auth = None + + def create_app_flow(self, app_flow_data: dict) -> None: + try: + app_data = app_flow_data["huawei-nce-app-flow:app-flows"]["applications"] + app_url = self._nce_fan_url + "/app-flows/apps" + LOGGER.info(f'Creating app: {app_data} URL: {app_url}') + requests.post(app_url, json=app_data, headers=HEADERS) + + app_flow_data = { + "app-flow": app_flow_data["huawei-nce-app-flow:app-flows"]["app-flow"] + } + app_flow_url = self._nce_fan_url + "/app-flows" + LOGGER.info(f'Creating app flow: {app_flow_data} URL: {app_flow_url}') + requests.post(app_flow_url, json=app_flow_data, headers=HEADERS) + except requests.exceptions.ConnectionError: + raise Exception("faild to send post requests to NCE FAN") + + def delete_app_flow(self, app_flow_name: str) -> None: + try: + app_url = self._nce_fan_url + f"/app-flows/apps/application={app_flow_name}" + LOGGER.info(f'Deleting app: {app_flow_name} URL: {app_url}') + requests.delete(app_url) + + app_flow_url = self._nce_fan_url + f"/app-flows/app-flow={app_flow_name}" + LOGGER.info(f'Deleting app flow: {app_flow_name} URL: {app_flow_url}') + requests.delete(app_flow_url) + except requests.exceptions.ConnectionError: + raise Exception("faild to send delete request to NCE FAN") diff --git a/src/device/tests/test_unitary_ietf_l3vpn.py b/src/device/tests/test_unitary_ietf_l3vpn.py new file mode 100644 index 0000000000000000000000000000000000000000..728ca691332c8abee7b5d6f5ad6c151240e540ed --- /dev/null +++ b/src/device/tests/test_unitary_ietf_l3vpn.py @@ -0,0 +1,345 @@ +import json +from json import dumps + +import requests + +from device.service.drivers.ietf_l3vpn.driver import IetfL3VpnDriver +from device.service.Tools import RESOURCE_ENDPOINTS + +settings = { + "endpoints": [ + { + "uuid": "access-pe", + "name": "access-pe", + "type": "copper", + "ce-ip": "1.1.1.1", + "address_ip": "3.3.2.1", + "address_prefix": 24, + "location": "access", + "mtu": 1500, + "ipv4_lan_prefixes": [ + {"lan": "128.32.10.0/24", "lan_tag": 10}, + {"lan": "128.32.20.0/24", "lan_tag": 20}, + ], + }, + { + "uuid": "cloud-pe", + "name": "cloud-pe", + "type": "copper", + "ce-ip": "1.1.1.1", + "address_ip": "3.3.2.1", + "address_prefix": 24, + "location": "cloud", + "mtu": 1500, + "ipv4_lan_prefixes": [{"lan": "172.1.101.0/24", "lan_tag": 101}], + }, + ], + "scheme": "http", + "username": "admin", + "password": "admin", + "base_url": "/restconf/v2/data", + "timeout": 120, + "verify": False, +} + +post_request_data = [] +get_request_data = [] + + +def mock_post(*args, **kwargs): + post_request_data.append((args, kwargs)) + + +def mock_get(*args, **kwargs): + get_request_data.append((args, kwargs)) + + +driver = IetfL3VpnDriver(address="1.2.3.4", port=0, **settings) + + +def test_connect(monkeypatch): + global post_request_data + global get_request_data + post_request_data = [] + get_request_data = [] + monkeypatch.setattr(requests, "post", mock_post) + monkeypatch.setattr(requests, "get", mock_get) + + driver.Connect() + assert not post_request_data + assert len(get_request_data) == 1 + assert get_request_data[0][0] == ( + "http://1.2.3.4:0/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services", + ) + assert list(get_request_data[0][1].keys()) == ["timeout", "verify", "auth"] + + +def test_GetConfig(monkeypatch): + global post_request_data + global get_request_data + post_request_data = [] + get_request_data = [] + monkeypatch.setattr(requests, "post", mock_post) + monkeypatch.setattr(requests, "get", mock_get) + + resources_to_get = [RESOURCE_ENDPOINTS] + result_GetConfig = driver.GetConfig(resources_to_get) + assert result_GetConfig == [ + ( + "/endpoints/endpoint[access-pe]", + { + "uuid": "access-pe", + "name": "access-pe", + "type": "copper", + "location": "access", + "ce-ip": "1.1.1.1", + "address_ip": "3.3.2.1", + "address_prefix": 24, + "mtu": 1500, + "ipv4_lan_prefixes": [ + {"lan": "128.32.10.0/24", "lan_tag": 10}, + {"lan": "128.32.20.0/24", "lan_tag": 20}, + ], + }, + ), + ( + "/endpoints/endpoint[cloud-pe]", + { + "uuid": "cloud-pe", + "name": "cloud-pe", + "type": "copper", + "location": "cloud", + "ce-ip": "1.1.1.1", + "address_ip": "3.3.2.1", + "address_prefix": 24, + "mtu": 1500, + "ipv4_lan_prefixes": [{"lan": "172.1.101.0/24", "lan_tag": 101}], + }, + ), + ] + + +def test_SetConfig(monkeypatch): + global post_request_data + global get_request_data + post_request_data = [] + get_request_data = [] + monkeypatch.setattr(requests, "post", mock_post) + monkeypatch.setattr(requests, "get", mock_get) + + resources = [ + ( + "/services/service[vpn_A]", + json.dumps( + { + "uuid": "vpn_A", + "src_device_name": "ip-net-controller", + "src_endpoint_name": settings["endpoints"][0]["name"], + "src_site_location": settings["endpoints"][0]["location"], + "src_ipv4_lan_prefixes": settings["endpoints"][0][ + "ipv4_lan_prefixes" + ], + "src_ce_address": settings["endpoints"][0]["ce-ip"], + "src_pe_address": settings["endpoints"][0]["address_ip"], + "src_ce_pe_network_prefix": settings["endpoints"][0][ + "address_prefix" + ], + "src_mtu": settings["endpoints"][0]["mtu"], + "dst_device_name": "ip-net-controller", + "dst_endpoint_name": settings["endpoints"][1]["name"], + "dst_site_location": settings["endpoints"][1]["location"], + "dst_ipv4_lan_prefixes": settings["endpoints"][1][ + "ipv4_lan_prefixes" + ], + "dst_ce_address": settings["endpoints"][1]["ce-ip"], + "dst_pe_address": settings["endpoints"][1]["address_ip"], + "dst_ce_pe_network_prefix": settings["endpoints"][1][ + "address_prefix" + ], + "dst_mtu": settings["endpoints"][1]["mtu"], + } + ), + ) + ] + result_SetConfig = driver.SetConfig(resources) + assert result_SetConfig == [("/services/service[vpn_A]", True)] + assert len(get_request_data) == 1 + assert get_request_data[0][0] == ( + "http://1.2.3.4:0/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service=vpn_A", + ) + assert len(post_request_data) == 1 + assert post_request_data[0][0] == ( + "http://1.2.3.4:0/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-services", + ) + assert post_request_data[0][1]["json"] == { + "ietf-l3vpn-svc:l3vpn-svc": { + "vpn-services": {"vpn-service": [{"vpn-id": "vpn_A"}]}, + "sites": { + "site": [ + { + "site-id": "site_access", + "management": {"type": "ietf-l3vpn-svc:customer-managed"}, + "locations": {"location": [{"location-id": "access"}]}, + "devices": { + "device": [ + { + "device-id": "ip-net-controller", + "location": "access", + } + ] + }, + "routing-protocols": { + "routing-protocol": [ + { + "type": "ietf-l3vpn-svc:static", + "static": { + "cascaded-lan-prefixes": { + "ipv4-lan-prefixes": [ + { + "lan": "128.32.10.0/24", + "lan-tag": 10, + "next-hop": "3.3.2.1", + }, + { + "lan": "128.32.20.0/24", + "lan-tag": 20, + "next-hop": "3.3.2.1", + }, + ] + } + }, + } + ] + }, + "site-network-accesses": { + "site-network-access": [ + { + "site-network-access-id": "access-pe", + "site-network-access-type": "ietf-l3vpn-svc:multipoint", + "device-reference": "ip-net-controller", + "vpn-attachment": { + "vpn-id": "vpn_A", + "site-role": "ietf-l3vpn-svc:hub-role", + }, + "ip-connection": { + "ipv4": { + "address-allocation-type": "ietf-l3vpn-svc:static-address", + "addresses": { + "provider-address": "3.3.2.1", + "customer-address": "1.1.1.1", + "prefix-length": 24, + }, + } + }, + "service": { + "svc-mtu": 1500, + "svc-input-bandwidth": 1000000000, + "svc-output-bandwidth": 1000000000, + "qos": { + "qos-profile": { + "classes": { + "class": [ + { + "class-id": "src_qos_profile", + "direction": ( + "ietf-l3vpn-svc:both", + ), + "latency": { + "latency-boundary": 10 + }, + "bandwidth": { + "guaranteed-bw-percent": 100 + }, + } + ] + } + } + }, + }, + } + ] + }, + }, + { + "site-id": "site_cloud", + "management": {"type": "ietf-l3vpn-svc:customer-managed"}, + "locations": {"location": [{"location-id": "cloud"}]}, + "devices": { + "device": [ + { + "device-id": "ip-net-controller", + "location": "cloud", + } + ] + }, + "routing-protocols": { + "routing-protocol": [ + { + "type": "ietf-l3vpn-svc:static", + "static": { + "cascaded-lan-prefixes": { + "ipv4-lan-prefixes": [ + { + "lan": "172.1.101.0/24", + "lan-tag": 101, + "next-hop": "3.3.2.1", + } + ] + } + }, + } + ] + }, + "site-network-accesses": { + "site-network-access": [ + { + "site-network-access-id": "cloud-pe", + "site-network-access-type": "ietf-l3vpn-svc:multipoint", + "device-reference": "ip-net-controller", + "vpn-attachment": { + "vpn-id": "vpn_A", + "site-role": "ietf-l3vpn-svc:spoke-role", + }, + "ip-connection": { + "ipv4": { + "address-allocation-type": "ietf-l3vpn-svc:static-address", + "addresses": { + "provider-address": "3.3.2.1", + "customer-address": "1.1.1.1", + "prefix-length": 24, + }, + } + }, + "service": { + "svc-mtu": 1500, + "svc-input-bandwidth": 1000000000, + "svc-output-bandwidth": 1000000000, + "qos": { + "qos-profile": { + "classes": { + "class": [ + { + "class-id": "dst_qos_profile", + "direction": ( + "ietf-l3vpn-svc:both", + ), + "latency": { + "latency-boundary": 10 + }, + "bandwidth": { + "guaranteed-bw-percent": 100 + }, + } + ] + } + } + }, + }, + } + ] + }, + }, + ] + }, + } + } diff --git a/src/nbi/service/__main__.py b/src/nbi/service/__main__.py index 71df0517aa688c106d8d0fe544f5f6b1af5a58f3..1d470f4eac30795e2272c9145baf947f3c982ba5 100644 --- a/src/nbi/service/__main__.py +++ b/src/nbi/service/__main__.py @@ -31,6 +31,7 @@ from .rest_server.nbi_plugins.ietf_network_slice import register_ietf_nss from .rest_server.nbi_plugins.ietf_acl import register_ietf_acl from .rest_server.nbi_plugins.qkd_app import register_qkd_app from .rest_server.nbi_plugins.tfs_api import register_tfs_api +from .rest_server.nbi_plugins import register_restconf from .context_subscription import register_context_subscription terminate = threading.Event() @@ -79,6 +80,7 @@ def main(): register_ietf_acl(rest_server) register_qkd_app(rest_server) register_tfs_api(rest_server) + register_restconf(rest_server) rest_server.start() register_context_subscription() diff --git a/src/nbi/service/rest_server/nbi_plugins/__init__.py b/src/nbi/service/rest_server/nbi_plugins/__init__.py index 53d5157f750bfb085125cbd33faff1cec5924e14..9b5d7920db49bab6d456aba186e6500142aad5b2 100644 --- a/src/nbi/service/rest_server/nbi_plugins/__init__.py +++ b/src/nbi/service/rest_server/nbi_plugins/__init__.py @@ -12,3 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. +from flask.json import jsonify +from flask_restful import Resource + +from nbi.service.rest_server.RestServer import RestServer + +from .tools.HttpStatusCodes import HTTP_CREATED + +URL_PREFIX = "/restconf/data" + + +class BaseServer(Resource): + def post(self): + response = jsonify({}) + response.status_code = HTTP_CREATED + return response + + +def _add_resource(rest_server: RestServer, resource: Resource, *urls, **kwargs): + urls = [(URL_PREFIX + url) for url in urls] + rest_server.add_resource(resource, *urls, **kwargs) + + +def register_restconf(rest_server: RestServer): + _add_resource(rest_server, BaseServer, "") diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py index 97364dff8606f1af48bab362b94b968561792411..bf3f8aabca1c04260d32a4163ec2686931f520fc 100644 --- a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py @@ -26,7 +26,7 @@ from ..tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK LOGGER = logging.getLogger(__name__) class L3VPN_Service(Resource): - @HTTP_AUTH.login_required + # @HTTP_AUTH.login_required def get(self, vpn_id : str): LOGGER.debug('VPN_Id: {:s}'.format(str(vpn_id))) LOGGER.debug('Request: {:s}'.format(str(request))) @@ -52,7 +52,7 @@ class L3VPN_Service(Resource): response.status_code = HTTP_SERVERERROR return response - @HTTP_AUTH.login_required + # @HTTP_AUTH.login_required def delete(self, vpn_id : str): LOGGER.debug('VPN_Id: {:s}'.format(str(vpn_id))) LOGGER.debug('Request: {:s}'.format(str(request))) diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py index 98d950952702d5cf1df8aa29edc50683e56a296e..47f6d5726225350976a67eeaacda64ceb32f0d7f 100644 --- a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py @@ -26,11 +26,11 @@ from .YangValidator import YangValidator LOGGER = logging.getLogger(__name__) class L3VPN_Services(Resource): - @HTTP_AUTH.login_required + # @HTTP_AUTH.login_required def get(self): return {} - @HTTP_AUTH.login_required + # @HTTP_AUTH.login_required def post(self): if not request.is_json: raise UnsupportedMediaType('JSON payload is required') request_data : Dict = request.json diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Service.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Service.py index e25270d3601ffa1cfdffa68a98305e3646602001..8f6dc86609f8c0e4d51fa02c64ab7c59a377395a 100644 --- a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Service.py +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Service.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,47 +13,61 @@ # limitations under the License. import logging + from flask.json import jsonify from flask_restful import Resource + from common.proto.context_pb2 import SliceStatusEnum from common.tools.context_queries.Slice import get_slice_by_uuid from common.tools.grpc.Tools import grpc_message_to_json from context.client.ContextClient import ContextClient from slice.client.SliceClient import SliceClient + from ..tools.Authentication import HTTP_AUTH -from ..tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK, HTTP_SERVERERROR +from ..tools.HttpStatusCodes import ( + HTTP_GATEWAYTIMEOUT, + HTTP_NOCONTENT, + HTTP_OK, + HTTP_SERVERERROR, +) LOGGER = logging.getLogger(__name__) + class NSS_Service(Resource): - @HTTP_AUTH.login_required - def get(self, slice_id : str): - LOGGER.debug('GET Slice ID: {:s}'.format(str(slice_id))) + # @HTTP_AUTH.login_required + def get(self, slice_id: str): + LOGGER.debug("GET Slice ID: {:s}".format(str(slice_id))) try: context_client = ContextClient() target = get_slice_by_uuid(context_client, slice_id, rw_copy=True) if target is None: - raise Exception('Slice({:s}) not found in database'.format(str(slice_id))) + raise Exception( + "Slice({:s}) not found in database".format(str(slice_id)) + ) - if target.slice_id.slice_uuid.uuid != slice_id: # pylint: disable=no-member - raise Exception('Slice retrieval failed. Wrong Slice Id was returned') + if target.slice_id.slice_uuid.uuid != slice_id: # pylint: disable=no-member + raise Exception("Slice retrieval failed. Wrong Slice Id was returned") slice_ready_status = SliceStatusEnum.SLICESTATUS_ACTIVE - slice_status = target.slice_status.slice_status # pylint: disable=no-member + slice_status = target.slice_status.slice_status # pylint: disable=no-member response = jsonify(grpc_message_to_json(target)) - response.status_code = HTTP_OK if slice_status == slice_ready_status else HTTP_GATEWAYTIMEOUT + response.status_code = ( + HTTP_OK if slice_status == slice_ready_status else HTTP_GATEWAYTIMEOUT + ) - except Exception as e: # pylint: disable=broad-except - LOGGER.exception('Something went wrong Retrieving Slice({:s})'.format(str(slice_id))) - response = jsonify({'error': str(e)}) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception( + "Something went wrong Retrieving Slice({:s})".format(str(slice_id)) + ) + response = jsonify({"error": str(e)}) response.status_code = HTTP_SERVERERROR return response - - @HTTP_AUTH.login_required - def delete(self, slice_id : str): - LOGGER.debug('DELETE Slice ID: {:s}'.format(str(slice_id))) + # @HTTP_AUTH.login_required + def delete(self, slice_id: str): + LOGGER.debug("DELETE Slice ID: {:s}".format(str(slice_id))) try: context_client = ContextClient() target = get_slice_by_uuid(context_client, slice_id) @@ -62,17 +76,25 @@ class NSS_Service(Resource): response.status_code = HTTP_OK if target is None: - LOGGER.warning('Slice({:s}) not found in database. Nothing done.'.format(str(slice_id))) + LOGGER.warning( + "Slice({:s}) not found in database. Nothing done.".format( + str(slice_id) + ) + ) response.status_code = HTTP_NOCONTENT else: - if target.slice_id.slice_uuid.uuid != slice_id: # pylint: disable=no-member - raise Exception('Slice retrieval failed. Wrong Slice Id was returned') + if target.slice_id.slice_uuid.uuid != slice_id and target.name != slice_id: # pylint: disable=no-member + raise Exception( + "Slice retrieval failed. Wrong Slice Id was returned" + ) slice_client = SliceClient() slice_client.DeleteSlice(target.slice_id) LOGGER.debug(f"Slice({slice_id}) successfully deleted") except Exception as e: # pylint: disable=broad-except - LOGGER.exception('Something went wrong Deleting Slice({:s})'.format(str(slice_id))) - response = jsonify({'error': str(e)}) + LOGGER.exception( + "Something went wrong Deleting Slice({:s})".format(str(slice_id)) + ) + response = jsonify({"error": str(e)}) response.status_code = HTTP_SERVERERROR - return response \ No newline at end of file + return response diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Service_Match_Criteria.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Service_Match_Criteria.py new file mode 100644 index 0000000000000000000000000000000000000000..3e1c9f73f97f0df8a2b271cb34a4852e983e77a1 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Service_Match_Criteria.py @@ -0,0 +1,59 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +from typing import Dict + +from flask import request +from flask.json import jsonify +from flask_restful import Resource +from werkzeug.exceptions import UnsupportedMediaType + +from context.client.ContextClient import ContextClient +from slice.client.SliceClient import SliceClient + +from ..tools.Authentication import HTTP_AUTH +from ..tools.HttpStatusCodes import ( + HTTP_CREATED, +) +from .ietf_slice_handler import IETFSliceHandler + +LOGGER = logging.getLogger(__name__) + + +class NSS_Service_Match_Criteria(Resource): + # @HTTP_AUTH.login_required + def get(self): + response = jsonify({"message": "All went well!"}) + # TODO Return list of current network-slice-services + return response + + # @HTTP_AUTH.login_required + def post(self, slice_id: str, sdp_id: str): + if not request.is_json: + raise UnsupportedMediaType("JSON payload is required") + request_data: Dict = request.json + context_client = ContextClient() + slice_request = IETFSliceHandler.create_match_criteria( + request_data, slice_id, sdp_id, context_client + ) + slice_client = SliceClient() + slice_client.UpdateSlice(slice_request) + slice_request = IETFSliceHandler.copy_candidate_ietf_slice_data_to_running( + slice_id, context_client + ) + _ = context_client.SetSlice(slice_request) + + response = jsonify({}) + response.status_code = HTTP_CREATED + return response diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Service_Match_Criterion.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Service_Match_Criterion.py new file mode 100644 index 0000000000000000000000000000000000000000..8fb8adfd98e461a43f2dc24121b84a59aaabbd6b --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Service_Match_Criterion.py @@ -0,0 +1,48 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging + +from flask.json import jsonify +from flask_restful import Resource + +from context.client.ContextClient import ContextClient + +from ..tools.Authentication import HTTP_AUTH +from ..tools.HttpStatusCodes import ( + HTTP_CREATED, +) +from .ietf_slice_handler import IETFSliceHandler + +LOGGER = logging.getLogger(__name__) + + +class NSS_Service_Match_Criterion(Resource): + # @HTTP_AUTH.login_required + def get(self): + response = jsonify({"message": "All went well!"}) + # TODO Return list of current network-slice-services + return response + + # @HTTP_AUTH.login_required + def delete(self, slice_id: str, sdp_id: str, match_criterion_id: str): + context_client = ContextClient() + slice_request = IETFSliceHandler.delete_match_criteria( + slice_id, sdp_id, int(match_criterion_id), context_client + ) + context_client = ContextClient() + _ = context_client.SetSlice(slice_request) + + response = jsonify({}) + response.status_code = HTTP_CREATED + return response diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services.py index 11a73141d6bd05db851d3019903e5a6db2f5c2d6..8398917a2c5d8c553efe59551962271d91759e9e 100644 --- a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services.py +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,112 +11,44 @@ # 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 ssl -import uuid from typing import Dict + +from flask import request from flask.json import jsonify from flask_restful import Resource -from flask import request - -from common.Constants import DEFAULT_CONTEXT_NAME -from common.proto.context_pb2 import Slice, SliceStatusEnum, EndPointId, Constraint -from common.tools.grpc.Tools import grpc_message_to_json -from ..tools.Authentication import HTTP_AUTH -from ..tools.HttpStatusCodes import HTTP_BADREQUEST, HTTP_OK, HTTP_CREATED, HTTP_SERVERERROR from werkzeug.exceptions import UnsupportedMediaType +from context.client.ContextClient import ContextClient from slice.client.SliceClient import SliceClient -from .bindings import load_json_data -from .bindings.network_slice_services import NetworkSliceServices + +from ..tools.HttpStatusCodes import HTTP_CREATED, HTTP_OK +from .ietf_slice_handler import IETFSliceHandler LOGGER = logging.getLogger(__name__) + class NSS_Services(Resource): - @HTTP_AUTH.login_required - def get(self): - response = jsonify({"message": "All went well!"}) - # TODO Return list of current network-slice-services + # @HTTP_AUTH.login_required + def get(self): + context_client = ContextClient() + ietf_slices = IETFSliceHandler.get_all_ietf_slices(context_client) + response = jsonify(ietf_slices) + response.status_code = HTTP_OK return response - @HTTP_AUTH.login_required + # @HTTP_AUTH.login_required def post(self): if not request.is_json: - raise UnsupportedMediaType('JSON payload is required') - request_data = json.dumps(request.json) + raise UnsupportedMediaType("JSON payload is required") + request_data: Dict = request.json + context_client = ContextClient() + slice_request = IETFSliceHandler.create_slice_service( + request_data, context_client + ) + slice_client = SliceClient() + slice_client.CreateSlice(slice_request) + response = jsonify({}) response.status_code = HTTP_CREATED - - slices: NetworkSliceServices = load_json_data(request_data, NetworkSliceServices)[0] - for ietf_slice in slices.slice_service: - slice_request: Slice = Slice() - # Meta information - # TODO implement name and owner based on "tags" - slice_request.slice_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME - slice_request.slice_id.slice_uuid.uuid = ietf_slice.service_id() - # TODO map with admin status of IETF Slice - slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED - - list_endpoints = [] - for sdp in ietf_slice.sdps().sdp: - endpoint = EndPointId() - endpoint.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME - endpoint.device_id.device_uuid.uuid = sdp.node_id() - endpoint.endpoint_uuid.uuid = sdp.sdp_id() - list_endpoints.append(endpoint) - slice_request.slice_endpoint_ids.extend(list_endpoints) - - # TODO Map connectivity_groups and connectivity constructs to real connections - LOGGER.debug(f"Connection groups detected: {len(ietf_slice.connection_groups().connection_group())}") - list_constraints = [] - for cg in ietf_slice.connection_groups().connection_group: - for cc in cg.connectivity_construct: - if cc.slo_sle_policy.custom: - with cc.slo_sle_policy.custom as slo: - for metric_bound in slo.service_slo_sle_policy().metric_bounds().metric_bound: - metric_type = str(metric_bound.metric_type()).casefold() - if metric_type == "service-slo-two-way-bandwidth": # TODO fix to two way! - constraint = Constraint() - metric_unit = metric_bound.metric_unit().casefold() - capacity = float(metric_bound.bound()) # Assuming capacity already in Gbps - if metric_unit == "mbps": - capacity /= 1E3 - elif metric_unit != "gbps": - LOGGER.warning(f"Invalided metric unit ({metric_bound.metric_unit()}), must be Mbps or Gbps") - response.status_code = HTTP_SERVERERROR - return response - constraint.sla_capacity.capacity_gbps = capacity - list_constraints.append(constraint) - - elif metric_type == "service-slo-one-way-delay": - if metric_bound.metric_unit().casefold() == "ms": - latency = int(metric_bound.bound()) - else: - LOGGER.warning(f"Invalided metric unit ({metric_bound.metric_unit()}), must be \"ms\" ") - response.status_code = HTTP_SERVERERROR - return response - constraint = Constraint() - constraint.sla_latency.e2e_latency_ms = latency - list_constraints.append(constraint) - - elif metric_type == "service-slo-availability": - availability = float(metric_bound.bound()) - if availability > 100.0 or availability < 0.0: - raise Exception(f'Slice SLO availability ({availability}) must be constrained [0,100]') - constraint = Constraint() - constraint.sla_availability.availability = availability - # TODO not really necessary, remove after OFC2023 - constraint.sla_availability.num_disjoint_paths = 0 - constraint.sla_availability.all_active = False - list_constraints.append(constraint) - - slice_request.slice_constraints.extend(list_constraints) - LOGGER.debug(grpc_message_to_json(slice_request)) # TODO remove - # TODO adding owner, needs to be recoded after updating the bindings - owner = request.json["data"]["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["service-tags"][0]["value"] - slice_request.slice_owner.owner_string = owner - slice_request.slice_owner.owner_uuid.uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, owner)) - slice_client = SliceClient() - slice_client.CreateSlice(slice_request) return response diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_Connection_Group.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_Connection_Group.py new file mode 100644 index 0000000000000000000000000000000000000000..0309c6ac475dec59d3219be79792fdef81e3d330 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_Connection_Group.py @@ -0,0 +1,75 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +from typing import Dict + +from flask import request +from flask.json import jsonify +from flask_restful import Resource +from werkzeug.exceptions import UnsupportedMediaType + +from context.client.ContextClient import ContextClient +from slice.client.SliceClient import SliceClient + +from ..tools.Authentication import HTTP_AUTH +from ..tools.HttpStatusCodes import HTTP_CREATED +from .ietf_slice_handler import IETFSliceHandler + +LOGGER = logging.getLogger(__name__) + + +class NSS_Service_Connection_Group(Resource): + # @HTTP_AUTH.login_required + def get(self): + response = jsonify({"message": "All went well!"}) + # TODO Return list of current network-slice-services + return response + + # @HTTP_AUTH.login_required + def put(self, slice_id: str, connection_group_id: str): + if not request.is_json: + raise UnsupportedMediaType("JSON payload is required") + request_data: Dict = request.json + + context_client = ContextClient() + slice_request = IETFSliceHandler.update_connection_group( + slice_id, request_data, context_client + ) + slice_client = SliceClient() + slice_client.UpdateSlice(slice_request) + slice_request = IETFSliceHandler.copy_candidate_ietf_slice_data_to_running( + slice_id, context_client + ) + _ = context_client.SetSlice(slice_request) + + response = jsonify({}) + response.status_code = HTTP_CREATED + return response + + # @HTTP_AUTH.login_required + def delete(self, slice_id: str, connection_group_id: str): + context_client = ContextClient() + slice_request = IETFSliceHandler.delete_connection_group( + slice_id, connection_group_id, context_client + ) + slice_client = SliceClient() + slice_client.UpdateSlice(slice_request) + slice_request = IETFSliceHandler.copy_candidate_ietf_slice_data_to_running( + slice_id, context_client + ) + _ = context_client.SetSlice(slice_request) + + response = jsonify({}) + response.status_code = HTTP_CREATED + return response diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_Connection_Groups.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_Connection_Groups.py new file mode 100644 index 0000000000000000000000000000000000000000..bee8349ef1949f0eb7f633ca4cbe6de6de1abbd6 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_Connection_Groups.py @@ -0,0 +1,52 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +from typing import Dict + +from flask import request +from flask.json import jsonify +from flask_restful import Resource +from werkzeug.exceptions import UnsupportedMediaType + +from context.client.ContextClient import ContextClient + +from ..tools.Authentication import HTTP_AUTH +from ..tools.HttpStatusCodes import HTTP_CREATED +from .ietf_slice_handler import IETFSliceHandler + +LOGGER = logging.getLogger(__name__) + + +class NSS_Service_Connection_Groups(Resource): + # @HTTP_AUTH.login_required + def get(self): + response = jsonify({"message": "All went well!"}) + # TODO Return list of current network-slice-services + return response + + # @HTTP_AUTH.login_required + def post(self, slice_id: str): + if not request.is_json: + raise UnsupportedMediaType("JSON payload is required") + request_data: Dict = request.json + + context_client = ContextClient() + slice_request = IETFSliceHandler.create_connection_group( + request_data, slice_id, context_client + ) + _ = context_client.SetSlice(slice_request) + + response = jsonify({}) + response.status_code = HTTP_CREATED + return response diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_SDP.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_SDP.py new file mode 100644 index 0000000000000000000000000000000000000000..f1d04a858907d016dc39a5a15a0d2771e25310af --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_SDP.py @@ -0,0 +1,45 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +from typing import Dict + +from flask import request +from flask.json import jsonify +from flask_restful import Resource +from werkzeug.exceptions import UnsupportedMediaType + +from context.client.ContextClient import ContextClient + +from ..tools.HttpStatusCodes import HTTP_OK +from .ietf_slice_handler import IETFSliceHandler + +LOGGER = logging.getLogger(__name__) + + +class NSS_Service_SDP(Resource): + # @HTTP_AUTH.login_required + def get(self): + response = jsonify({"message": "All went well!"}) + # TODO Return list of current network-slice-services + return response + + # @HTTP_AUTH.login_required + def delete(self, slice_id: str, sdp_id: str): + context_client = ContextClient() + slice_request = IETFSliceHandler.delete_sdp(slice_id, sdp_id, context_client) + _ = context_client.SetSlice(slice_request) + + response = jsonify({}) + response.status_code = HTTP_OK + return response diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_SDPs.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_SDPs.py new file mode 100644 index 0000000000000000000000000000000000000000..8a3fb8c4210b790dd25f9c0f8467b79ef5a86bc9 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/NSS_Services_SDPs.py @@ -0,0 +1,51 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +from typing import Dict + +from flask import request +from flask.json import jsonify +from flask_restful import Resource +from werkzeug.exceptions import UnsupportedMediaType + +from context.client.ContextClient import ContextClient + +from ..tools.HttpStatusCodes import HTTP_CREATED +from .ietf_slice_handler import IETFSliceHandler + +LOGGER = logging.getLogger(__name__) + + +class NSS_Service_SDPs(Resource): + # @HTTP_AUTH.login_required + def get(self): + response = jsonify({"message": "All went well!"}) + # TODO Return list of current network-slice-services + return response + + # @HTTP_AUTH.login_required + def post(self, slice_id: str): + if not request.is_json: + raise UnsupportedMediaType("JSON payload is required") + request_data: Dict = request.json + + context_client = ContextClient() + slice_request = IETFSliceHandler.create_sdp( + request_data, slice_id, context_client + ) + _ = context_client.SetSlice(slice_request) + + response = jsonify({}) + response.status_code = HTTP_CREATED + return response diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/YangValidator.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/YangValidator.py new file mode 100644 index 0000000000000000000000000000000000000000..77071f7f7a72ad7d46e34f118a87dc2280a2a24c --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/YangValidator.py @@ -0,0 +1,36 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import libyang, os +from typing import Dict, Optional + +YANG_DIR = os.path.join(os.path.dirname(__file__), 'yang') + +class YangValidator: + def __init__(self, module_name : str) -> None: + self._yang_context = libyang.Context(YANG_DIR) + self._yang_module = self._yang_context.load_module(module_name) + self._yang_module.feature_enable_all() + + def parse_to_dict(self, message : Dict) -> Dict: + dnode : Optional[libyang.DNode] = self._yang_module.parse_data_dict( + message, validate_present=True, validate=True, strict=True + ) + if dnode is None: raise Exception('Unable to parse Message({:s})'.format(str(message))) + message = dnode.print_dict() + dnode.free() + return message + + def destroy(self) -> None: + self._yang_context.destroy() diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/__init__.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/__init__.py index e900c27e96aafc248e5db1bec303cb25b5a0f2d7..db76b3b911c971df8ab81b1710f4ac80e4ccb3ad 100644 --- a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/__init__.py +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,16 +16,60 @@ # Ref: https://datatracker.ietf.org/doc/draft-ietf-teas-ietf-network-slice-nbi-yang/ from flask_restful import Resource + from nbi.service.rest_server.RestServer import RestServer -from .NSS_Services import NSS_Services + from .NSS_Service import NSS_Service +from .NSS_Service_Match_Criteria import NSS_Service_Match_Criteria +from .NSS_Service_Match_Criterion import NSS_Service_Match_Criterion +from .NSS_Services import NSS_Services +from .NSS_Services_Connection_Group import NSS_Service_Connection_Group +from .NSS_Services_Connection_Groups import NSS_Service_Connection_Groups +from .NSS_Services_SDP import NSS_Service_SDP +from .NSS_Services_SDPs import NSS_Service_SDPs + +URL_PREFIX = "/restconf/data/ietf-network-slice-service" -URL_PREFIX = '/restconf/data/ietf-network-slice-service:ietf-nss' -def _add_resource(rest_server : RestServer, resource : Resource, *urls, **kwargs): +def _add_resource(rest_server: RestServer, resource: Resource, *urls, **kwargs): urls = [(URL_PREFIX + url) for url in urls] rest_server.add_resource(resource, *urls, **kwargs) -def register_ietf_nss(rest_server : RestServer): - _add_resource(rest_server, NSS_Services, '/network-slice-services') - _add_resource(rest_server, NSS_Service, '/network-slice-services/slice-service=<string:slice_id>') + +def register_ietf_nss(rest_server: RestServer): + _add_resource(rest_server, NSS_Services, ":network-slice-services") + _add_resource( + rest_server, + NSS_Service, + ":network-slice-services/slice-service=<string:slice_id>", + ) + _add_resource( + rest_server, + NSS_Service_SDPs, + ":network-slice-services/slice-service=<string:slice_id>/sdps", + ) + _add_resource( + rest_server, + NSS_Service_SDP, + ":network-slice-services/slice-service=<string:slice_id>/sdps/sdp=<string:sdp_id>", + ) + _add_resource( + rest_server, + NSS_Service_Connection_Groups, + ":network-slice-services/slice-service=<string:slice_id>/connection-groups", + ) + _add_resource( + rest_server, + NSS_Service_Connection_Group, + ":network-slice-services/slice-service=<string:slice_id>/connection-groups/connection-group=<string:connection_group_id>", + ) + _add_resource( + rest_server, + NSS_Service_Match_Criteria, + ":network-slice-services/slice-service=<string:slice_id>/sdps/sdp=<string:sdp_id>/service-match-criteria", + ) + _add_resource( + rest_server, + NSS_Service_Match_Criterion, + ":network-slice-services/slice-service=<string:slice_id>/sdps/sdp=<string:sdp_id>/service-match-criteria/match-criterion=<string:match_criterion_id>", + ) diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/ietf_slice_handler.py b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/ietf_slice_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..80ce4c6b78d446ff1e08a750f236e0c143e1ba57 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/ietf_slice_handler.py @@ -0,0 +1,640 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging +import uuid +from typing import Dict, List, Optional + +from common.Constants import DEFAULT_CONTEXT_NAME +from common.proto.context_pb2 import ( + ConfigRule, + Constraint, + DeviceId, + Device, + Empty, + EndPointId, + ServiceConfig, + Slice, + SliceStatusEnum, +) +from common.tools.context_queries.Slice import get_slice_by_defualt_name +from common.tools.grpc.ConfigRules import update_config_rule_custom +from common.tools.object_factory.Device import json_device_id +from common.DeviceTypes import DeviceTypeEnum +from context.client import ContextClient + +from .YangValidator import YangValidator + +LOGGER = logging.getLogger(__name__) + + +RUNNING_RESOURCE_KEY = "running_ietf_slice" +CANDIDATE_RESOURCE_KEY = "candidate_ietf_slice" +ADDRESS_PREFIX = 24 +RAISE_IF_DIFFERS = False + + +def validate_ietf_slice_data(request_data: Dict) -> None: + """ + Validate the provided IETF slice data against the YANG model. + """ + yang_validator = YangValidator("ietf-network-slice-service") + _ = yang_validator.parse_to_dict(request_data) + yang_validator.destroy() + + +def get_custom_config_rule( + service_config: ServiceConfig, resource_key: str +) -> Optional[ConfigRule]: + """ + Retrieve the custom config rule with the given resource_key from a ServiceConfig. + """ + for cr in service_config.config_rules: + if ( + cr.WhichOneof("config_rule") == "custom" + and cr.custom.resource_key == resource_key + ): + return cr + return None + + +def get_ietf_data_from_config(slice_request: Slice, resource_key: str) -> Dict: + """ + Retrieve the IETF data (as a Python dict) from a slice's config rule for the specified resource_key. + Raises an exception if not found. + """ + config_rule = get_custom_config_rule(slice_request.slice_config, resource_key) + if not config_rule: + raise Exception(f"IETF data not found for resource_key: {resource_key}") + return json.loads(config_rule.custom.resource_value) + + +def update_ietf_data_in_config( + slice_request: Slice, resource_key: str, ietf_data: Dict +) -> None: + """ + Update the slice config rule (identified by resource_key) with the provided IETF data. + """ + fields = {name: (value, RAISE_IF_DIFFERS) for name, value in ietf_data.items()} + update_config_rule_custom( + slice_request.slice_config.config_rules, resource_key, fields + ) + + +def build_constraints_from_connection_group(connection_group: dict) -> List[Constraint]: + """ + Build a list of Constraints from the 'metric-bound' data in a connection group. + """ + constraints = [] + metric_bounds = connection_group["connectivity-construct"][0][ + "service-slo-sle-policy" + ]["slo-policy"]["metric-bound"] + + for metric in metric_bounds: + metric_type = metric["metric-type"] + if metric_type == "ietf-nss:one-way-delay-maximum": + bound_value = float(metric["bound"]) + constraint = Constraint() + constraint.sla_latency.e2e_latency_ms = bound_value + constraints.append(constraint) + elif metric_type == "ietf-nss:one-way-bandwidth": + bound_value = float(metric["bound"]) + constraint = Constraint() + # Convert from Mbps to Gbps if needed + constraint.sla_capacity.capacity_gbps = bound_value / 1.0e3 + constraints.append(constraint) + + return constraints + + +def get_endpoint_controller_type( + endpoint: EndPointId, context_client: ContextClient +) -> str: + """ + Retrieve the device type of an endpoint's controller device, if any; otherwise returns an empty string. + """ + endpoint_device: Device = context_client.GetDevice(endpoint.device_id) + if endpoint_device.controller_id == DeviceId(): + return "" + controller = context_client.GetDevice(endpoint_device.controller_id) + if controller is None: + controller_uuid = endpoint_device.controller_id.device_uuid.uuid + raise Exception(f"Controller device {controller_uuid} not found") + return controller.device_type + + +def sort_endpoints( + endpoints_list: List[EndPointId], + sdps: List, + connection_group: Dict, + context_client: ContextClient, +) -> List[EndPointId]: + """ + Sort the endpoints_list based on controller type: + - If the first endpoint is an NCE, keep order. + - If the last endpoint is an NCE, reverse order. + - Otherwise, use the 'p2p-sender-sdp' from the connection group to decide. + """ + if not endpoints_list: + return endpoints_list + + first_ep = endpoints_list[0] + last_ep = endpoints_list[-1] + first_controller_type = get_endpoint_controller_type(first_ep, context_client) + last_controller_type = get_endpoint_controller_type(last_ep, context_client) + + if first_controller_type == DeviceTypeEnum.NCE.value: + return endpoints_list + elif last_controller_type == DeviceTypeEnum.NCE.value: + return endpoints_list[::-1] + + src_sdp_id = connection_group["connectivity-construct"][0]["p2p-sender-sdp"] + sdp_id_name_mapping = {sdp["id"]: sdp["node-id"] for sdp in sdps} + if endpoints_list[0].device_id.device_uuid.uuid == sdp_id_name_mapping[src_sdp_id]: + return endpoints_list + return endpoints_list[::-1] + + +def replace_ont_endpoint_with_emu_dc( + endpoint_list: List[EndPointId], context_client: ContextClient +) -> List[EndPointId]: + """ + Replace an ONT endpoint in endpoint_list with an 'emu-datacenter' endpoint if found. + One endpoint must be managed (controller_id != empty), the other must be unmanaged. + """ + if len(endpoint_list) != 2: + raise Exception( + "Expecting exactly two endpoints to handle ONT -> emu-dc replacement" + ) + + link_list = context_client.ListLinks(Empty()) + links = list(link_list.links) + devices_list = context_client.ListDevices(Empty()) + devices = devices_list.devices + + uuid_name_map = {d.device_id.device_uuid.uuid: d.name for d in devices} + uuid_device_map = {d.device_id.device_uuid.uuid: d for d in devices} + name_device_map = {d.name: d for d in devices} + + endpoint_id_1, endpoint_id_2 = endpoint_list + device_uuid_1 = endpoint_id_1.device_id.device_uuid.uuid + device_uuid_2 = endpoint_id_2.device_id.device_uuid.uuid + + device_1 = name_device_map.get(device_uuid_1) + device_2 = name_device_map.get(device_uuid_2) + + if not device_1 or not device_2: + raise Exception("One or both devices not found in name_device_map") + + # Check if the first endpoint is managed + if device_1.controller_id != DeviceId(): + for link in links: + link_endpoints = list(link.link_endpoint_ids) + link_ep_1, link_ep_2 = link_endpoints + if ( + device_uuid_1 == uuid_name_map.get(link_ep_1.device_id.device_uuid.uuid) + and uuid_device_map[link_ep_2.device_id.device_uuid.uuid].device_type + == "emu-datacenter" + ): + endpoint_list[0] = link_ep_2 + break + # Otherwise, check if the second endpoint is managed + elif device_2.controller_id != DeviceId(): + for link in links: + link_endpoints = list(link.link_endpoint_ids) + link_ep_1, link_ep_2 = link_endpoints + if ( + device_uuid_2 == uuid_name_map.get(link_ep_1.device_id.device_uuid.uuid) + and uuid_device_map[link_ep_2.device_id.device_uuid.uuid].device_type + == "emu-datacenter" + ): + endpoint_list[1] = link_ep_2 + break + else: + raise Exception( + "One endpoint should be managed by a controller and the other should not be" + ) + + return endpoint_list + + +class IETFSliceHandler: + @staticmethod + def get_all_ietf_slices(context_client: ContextClient) -> Dict: + """ + Retrieve all IETF slices from the (single) context. Expects exactly one context in the system. + """ + existing_context_ids = context_client.ListContextIds(Empty()) + context_ids = list(existing_context_ids.context_ids) + if len(context_ids) != 1: + raise Exception("Number of contexts should be exactly 1") + + slices_list = context_client.ListSlices(context_ids[0]) + slices = slices_list.slices + + ietf_slices = {"network-slice-services": {"slice-service": []}} + for slc in slices: + candidate_cr = get_custom_config_rule( + slc.slice_config, CANDIDATE_RESOURCE_KEY + ) + if not candidate_cr: + # Skip slices that don't have the candidate_ietf_slice data + continue + candidate_ietf_data = json.loads(candidate_cr.custom.resource_value) + ietf_slices["network-slice-services"]["slice-service"].append( + candidate_ietf_data["network-slice-services"]["slice-service"][0] + ) + return ietf_slices + + @staticmethod + def create_slice_service( + request_data: dict, context_client: ContextClient + ) -> Slice: + """ + Create a new slice service from the provided IETF data, applying validations and constructing a Slice object. + """ + # Ensure the top-level key is "network-slice-services" + if "network-slice-services" not in request_data: + request_data = {"network-slice-services": request_data} + + validate_ietf_slice_data(request_data) + slice_service = request_data["network-slice-services"]["slice-service"][0] + + slice_id = slice_service["id"] + sdps = slice_service["sdps"]["sdp"] + if len(sdps) != 2: + raise Exception("Number of SDPs should be exactly 2") + + connection_groups = slice_service["connection-groups"]["connection-group"] + slice_request = Slice() + slice_request.slice_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME + slice_request.slice_id.slice_uuid.uuid = slice_id + slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED + + list_endpoints = [] + endpoint_config_rules = [] + connection_group_ids = set() + + # Build endpoints from SDPs + for sdp in sdps: + attachment_circuits = sdp["attachment-circuits"]["attachment-circuit"] + if len(attachment_circuits) != 1: + raise Exception("Each SDP must have exactly 1 attachment-circuit") + + endpoint = EndPointId() + endpoint.topology_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME + device_uuid = sdp["node-id"] + endpoint.device_id.device_uuid.uuid = device_uuid + endpoint_uuid = attachment_circuits[0]["ac-tp-id"] + endpoint.endpoint_uuid.uuid = endpoint_uuid + list_endpoints.append(endpoint) + + # Keep track of connection-group-id from each SDP + connection_group_ids.add( + sdp["service-match-criteria"]["match-criterion"][0][ + "target-connection-group-id" + ] + ) + + # Endpoint-specific config rule fields + endpoint_config_rule_fields = { + "address_ip": (endpoint_uuid, RAISE_IF_DIFFERS), + "address_prefix": (ADDRESS_PREFIX, RAISE_IF_DIFFERS), + } + endpoint_config_rules.append( + ( + f"/device[{device_uuid}]/endpoint[{endpoint_uuid}]/settings", + endpoint_config_rule_fields, + ) + ) + + if len(connection_group_ids) != 1: + raise Exception("SDPs do not share a common connection-group-id") + + # Build constraints from the matching connection group + unique_cg_id = connection_group_ids.pop() + found_cg = next( + (cg for cg in connection_groups if cg["id"] == unique_cg_id), None + ) + if not found_cg: + raise Exception("The connection group referenced by the SDPs was not found") + + list_constraints = build_constraints_from_connection_group(found_cg) + + # Sort endpoints and optionally replace the ONT endpoint + list_endpoints = sort_endpoints(list_endpoints, sdps, found_cg, context_client) + list_endpoints = replace_ont_endpoint_with_emu_dc( + list_endpoints, context_client + ) + + slice_request.slice_endpoint_ids.extend(list_endpoints) + slice_request.slice_constraints.extend(list_constraints) + + # Set slice owner + slice_request.slice_owner.owner_string = slice_id + slice_request.slice_owner.owner_uuid.uuid = str( + uuid.uuid5(uuid.NAMESPACE_DNS, slice_id) + ) + + # Update slice config with IETF data (both running and candidate) + ietf_slice_fields = { + name: (value, RAISE_IF_DIFFERS) for name, value in request_data.items() + } + update_config_rule_custom( + slice_request.slice_config.config_rules, + RUNNING_RESOURCE_KEY, + ietf_slice_fields, + ) + update_config_rule_custom( + slice_request.slice_config.config_rules, + CANDIDATE_RESOURCE_KEY, + ietf_slice_fields, + ) + + # Update endpoint config rules + for ep_cr_key, ep_cr_fields in endpoint_config_rules: + update_config_rule_custom( + slice_request.slice_config.config_rules, ep_cr_key, ep_cr_fields + ) + + return slice_request + + @staticmethod + def create_sdp( + request_data: dict, slice_uuid: str, context_client: ContextClient + ) -> Slice: + """ + Add a new SDP to an existing slice, updating the candidate IETF data. + """ + sdps = request_data["sdp"] + if len(sdps) != 1: + raise Exception("Number of SDPs to create must be exactly 1") + + new_sdp = sdps[0] + slice_request = get_slice_by_defualt_name( + context_client, slice_uuid, rw_copy=False + ) + ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY) + + slice_service = ietf_data["network-slice-services"]["slice-service"][0] + slice_sdps = slice_service["sdps"]["sdp"] + slice_sdps.append(new_sdp) + + # Save updated IETF data + update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data) + return slice_request + + @staticmethod + def delete_sdp( + slice_uuid: str, sdp_id: str, context_client: ContextClient + ) -> Slice: + """ + Delete the specified SDP from an existing slice's candidate IETF data. + """ + slice_request = get_slice_by_defualt_name( + context_client, slice_uuid, rw_copy=False + ) + ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY) + + slice_service = ietf_data["network-slice-services"]["slice-service"][0] + slice_sdps = slice_service["sdps"]["sdp"] + + # Find and remove the matching SDP + sdp_idx = next( + (i for i, sdp in enumerate(slice_sdps) if sdp["id"] == sdp_id), None + ) + if sdp_idx is None: + raise Exception(f"SDP with id '{sdp_id}' not found in slice '{slice_uuid}'") + slice_sdps.pop(sdp_idx) + + update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data) + return slice_request + + @staticmethod + def create_connection_group( + request_data: dict, slice_id: str, context_client: ContextClient + ) -> Slice: + """ + Add a new connection group to an existing slice's candidate IETF data. + """ + connection_groups = request_data["connection-group"] + if len(connection_groups) != 1: + raise Exception("Number of connection groups to create must be exactly 1") + + new_connection_group = connection_groups[0] + slice_request = get_slice_by_defualt_name( + context_client, slice_id, rw_copy=False + ) + ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY) + + slice_service = ietf_data["network-slice-services"]["slice-service"][0] + slice_connection_groups = slice_service["connection-groups"]["connection-group"] + slice_connection_groups.append(new_connection_group) + + # Validate the updated data, then save + validate_ietf_slice_data(ietf_data) + update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data) + return slice_request + + @staticmethod + def update_connection_group( + slice_name: str, + updated_connection_group: dict, + context_client: ContextClient, + ) -> Slice: + """ + Update an existing connection group in the candidate IETF data. + """ + slice_request = get_slice_by_defualt_name( + context_client, slice_name, rw_copy=False + ) + candidate_ietf_data = get_ietf_data_from_config( + slice_request, CANDIDATE_RESOURCE_KEY + ) + + slice_service = candidate_ietf_data["network-slice-services"]["slice-service"][ + 0 + ] + slice_connection_groups = slice_service["connection-groups"]["connection-group"] + + cg_id = updated_connection_group["id"] + cg_idx = next( + (i for i, cg in enumerate(slice_connection_groups) if cg["id"] == cg_id), + None, + ) + if cg_idx is None: + raise Exception(f"Connection group with id '{cg_id}' not found") + + slice_connection_groups[cg_idx] = updated_connection_group + update_ietf_data_in_config( + slice_request, CANDIDATE_RESOURCE_KEY, candidate_ietf_data + ) + + slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED + return slice_request + + @staticmethod + def delete_connection_group( + slice_uuid: str, connection_group_id: str, context_client: ContextClient + ) -> Slice: + """ + Remove an existing connection group from the candidate IETF data of a slice. + """ + slice_request = get_slice_by_defualt_name( + context_client, slice_uuid, rw_copy=False + ) + candidate_ietf_data = get_ietf_data_from_config( + slice_request, CANDIDATE_RESOURCE_KEY + ) + + slice_service = candidate_ietf_data["network-slice-services"]["slice-service"][ + 0 + ] + slice_connection_groups = slice_service["connection-groups"]["connection-group"] + + cg_idx = next( + ( + i + for i, cg in enumerate(slice_connection_groups) + if cg["id"] == connection_group_id + ), + None, + ) + if cg_idx is None: + raise Exception( + f"Connection group with id '{connection_group_id}' not found" + ) + + slice_connection_groups.pop(cg_idx) + update_ietf_data_in_config( + slice_request, CANDIDATE_RESOURCE_KEY, candidate_ietf_data + ) + + slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED + return slice_request + + @staticmethod + def create_match_criteria( + request_data: dict, slice_name: str, sdp_id: str, context_client: ContextClient + ) -> Slice: + """ + Create a new match-criterion for the specified SDP in a slice's candidate IETF data. + """ + match_criteria = request_data["match-criterion"] + if len(match_criteria) != 1: + raise Exception( + "Number of match-criterion entries to create must be exactly 1" + ) + + new_match_criterion = match_criteria[0] + target_connection_group_id = new_match_criterion["target-connection-group-id"] + + slice_request = get_slice_by_defualt_name( + context_client, slice_name, rw_copy=False + ) + ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY) + + slice_service = ietf_data["network-slice-services"]["slice-service"][0] + connection_groups = slice_service["connection-groups"]["connection-group"] + sdps = slice_service["sdps"]["sdp"] + + # Find the referenced connection group + found_cg = next( + (cg for cg in connection_groups if cg["id"] == target_connection_group_id), + None, + ) + if not found_cg: + raise Exception( + f"Connection group '{target_connection_group_id}' not found" + ) + + # Build constraints from that connection group + list_constraints = build_constraints_from_connection_group(found_cg) + + # Add match-criterion to the relevant SDP + sdp_to_update = next((s for s in sdps if s["id"] == sdp_id), None) + if not sdp_to_update: + raise Exception(f"SDP '{sdp_id}' not found") + + sdp_to_update["service-match-criteria"]["match-criterion"].append( + new_match_criterion + ) + + # Update constraints at the slice level as needed + del slice_request.slice_constraints[:] + slice_request.slice_constraints.extend(list_constraints) + slice_request.slice_status.slice_status = SliceStatusEnum.SLICESTATUS_PLANNED + + update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data) + return slice_request + + @staticmethod + def delete_match_criteria( + slice_uuid: str, + sdp_id: str, + match_criterion_id: int, + context_client: ContextClient, + ) -> Slice: + """ + Delete the specified match-criterion from an SDP in the slice's candidate IETF data. + """ + slice_request = get_slice_by_defualt_name( + context_client, slice_uuid, rw_copy=False + ) + ietf_data = get_ietf_data_from_config(slice_request, CANDIDATE_RESOURCE_KEY) + + slice_service = ietf_data["network-slice-services"]["slice-service"][0] + sdps = slice_service["sdps"]["sdp"] + + # Find and modify the specified SDP + sdp_to_update = next((s for s in sdps if s["id"] == sdp_id), None) + if not sdp_to_update: + raise Exception(f"SDP '{sdp_id}' not found in slice '{slice_uuid}'") + + match_criteria = sdp_to_update["service-match-criteria"]["match-criterion"] + mc_index = next( + ( + i + for i, m in enumerate(match_criteria) + if m["index"] == match_criterion_id + ), + None, + ) + if mc_index is None: + raise Exception( + f"No match-criterion with index '{match_criterion_id}' found in SDP '{sdp_id}'" + ) + + match_criteria.pop(mc_index) + update_ietf_data_in_config(slice_request, CANDIDATE_RESOURCE_KEY, ietf_data) + return slice_request + + @staticmethod + def copy_candidate_ietf_slice_data_to_running( + slice_uuid: str, context_client: ContextClient + ) -> Slice: + """ + Copy candidate IETF slice data to the running IETF slice data for a given slice. + """ + slice_request = get_slice_by_defualt_name( + context_client, slice_uuid, rw_copy=False + ) + candidate_ietf_data = get_ietf_data_from_config( + slice_request, CANDIDATE_RESOURCE_KEY + ) + update_ietf_data_in_config( + slice_request, RUNNING_RESOURCE_KEY, candidate_ietf_data + ) + return slice_request diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-ac-common@2023-11-13.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-ac-common@2023-11-13.yang new file mode 100644 index 0000000000000000000000000000000000000000..170e70fff67242b380fc984d815e3d83557eca06 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-ac-common@2023-11-13.yang @@ -0,0 +1,1651 @@ +module ietf-ac-common { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-ac-common"; + prefix ac-common; + + import ietf-vpn-common { + prefix vpn-common; + reference + "RFC 9181: A Common YANG Data Model for Layer 2 and Layer 3 + VPNs"; + } + import ietf-netconf-acm { + prefix nacm; + reference + "RFC 8341: Network Configuration Access Control Model"; + } + import ietf-inet-types { + prefix inet; + reference + "RFC 6991: Common YANG Data Types, Section 4"; + } + import ietf-yang-types { + prefix yang; + reference + "RFC 6991: Common YANG Data Types, Section 3"; + } + import ietf-key-chain { + prefix key-chain; + reference + "RFC 8177: YANG Data Model for Key Chains"; + } + + organization + "IETF OPSAWG (Operations and Management Area Working Group)"; + contact + "WG Web: <https://datatracker.ietf.org/wg/opsawg/> + WG List: <mailto:opsawg@ietf.org> + + Editor: Mohamed Boucadair + <mailto:mohamed.boucadair@orange.com> + Author: Richard Roberts + <mailto:rroberts@juniper.net> + Author: Oscar Gonzalez de Dios + <mailto:oscar.gonzalezdedios@telefonica.com> + Author: Samier Barguil + <mailto:ssamier.barguil_giraldo@nokia.com> + Author: Bo Wu + <mailto:lana.wubo@huawei.com>"; + description + "This YANG module defines a common attachment circuit (AC) + YANG model. + + Copyright (c) 2024 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Revised BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC XXXX; see the + RFC itself for full legal notices."; + + revision 2023-11-13 { + description + "Initial revision."; + reference + "RFC XXXX: A Common YANG Data Model for Attachment Circuits"; + } + + /****************************Features************************/ + + feature layer2-ac { + description + "Indicates support of Layer 2 ACs."; + } + + feature layer3-ac { + description + "Indicates support of Layer 3 ACs."; + } + + feature server-assigned-reference { + description + "This feature indicates support for server-generated references + and use of such references to access related resources."; + } + + /****************************Identities************************/ + // IP address allocation types + + identity address-allocation-type { + description + "Base identity for address allocation type in the AC."; + } + + identity provider-dhcp { + base address-allocation-type; + description + "The provider's network provides a DHCP service to the + customer."; + } + + identity provider-dhcp-relay { + base address-allocation-type; + description + "The provider's network provides a DHCP relay service to the + customer."; + } + + identity provider-dhcp-slaac { + if-feature "vpn-common:ipv6"; + base address-allocation-type; + description + "The provider's network provides a DHCP service to the customer + as well as IPv6 Stateless Address Autoconfiguration (SLAAC)."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + } + + identity static-address { + base address-allocation-type; + description + "The provider's network provides static IP addressing to the + customer."; + } + + identity slaac { + if-feature "vpn-common:ipv6"; + base address-allocation-type; + description + "The provider's network uses IPv6 SLAAC to provide addressing + to the customer."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + } + + identity dynamic-infra { + base address-allocation-type; + description + "The IP address is dynamically allocated by the hosting + infrastrcture."; + } + + // next-hop actions + + identity local-defined-next-hop { + description + "Base identity of local defined next hops."; + } + + identity discard { + base local-defined-next-hop; + description + "Indicates an action to discard traffic for the corresponding + destination. For example, this can be used to black-hole + traffic."; + } + + identity local-link { + base local-defined-next-hop; + description + "Treat traffic towards addresses within the specified next-hop + prefix as though they are connected to a local link."; + } + + // Layer 2 tunnel types + + identity l2-tunnel-type { + description + "Base identity for Layer 2 tunnel selection for an AC."; + } + + identity pseudowire { + base l2-tunnel-type; + description + "Pseudowire tunnel termination for the AC."; + } + + identity vpls { + base l2-tunnel-type; + description + "Virtual Private LAN Service (VPLS) tunnel termination for + the AC."; + } + + identity vxlan { + base l2-tunnel-type; + description + "Virtual eXtensible Local Area Network (VXLAN) tunnel + termination for the AC."; + } + + // Layer 3 tunnel types + + identity l3-tunnel-type { + description + "Base identity for Layer 3 tunnel selection for an AC."; + } + + identity ip-in-ip { + base l3-tunnel-type; + description + "IP in IP Tunneling."; + } + + identity ipsec { + base l3-tunnel-type; + description + "IP Security (IPsec)."; + } + + identity gre { + base l3-tunnel-type; + description + "Generic Routing Encapsulation (GRE)."; + } + + // Tagging precedence + + identity precedence-type { + description + "Redundancy type. The service can be created with primary and + secondary tagging."; + } + + identity primary { + base precedence-type; + description + "Identifies the main attachment circuit."; + } + + identity secondary { + base precedence-type; + description + "Identifies the secondary attachment circuit."; + } + // AC Type + + identity role { + description + "Base identity for the network role of an AC."; + } + + identity uni { + base role; + description + "User-to-Network Interface (UNI)."; + } + + identity nni { + base role; + description + "Network-to-Network Interface (NNI)."; + } + + identity public-nni { + base role; + description + "Public peering."; + } + + // More Admin status types + + identity awaiting-validation { + base vpn-common:administrative-status; + description + "This administrative status reflects that a request is + pending an adiministrator approval."; + } + + identity awaiting-processing { + base vpn-common:administrative-status; + description + "This administrative status reflects that a request was + approved and validated, but is awaiting more processing + before activation."; + } + + identity admin-prohibited { + base vpn-common:administrative-status; + description + "This administrative status reflects that a request cannot + be handled because of administrative policies."; + } + identity rejected { + base vpn-common:administrative-status; + description + "This administrative status reflects that a request was + rejected because, e.g., there are no sufficient resources + or other reasons not covered by the other status types."; + } + + identity bgp-role { + description + "Used to indicate BGP role when establishing a BGP session."; + reference + "RFC 9234: Route Leak Prevention and Detection Using + Roles in UPDATE and OPEN Messages, Section 4"; + } + + identity provider { + base bgp-role; + description + "The local AS is a transit provider of the remote AS."; + } + + identity client { + base bgp-role; + description + "The local AS is a transit provider of the remote AS."; + } + + identity rs { + base bgp-role; + description + "The local AS is a Route Server (RS)."; + } + + identity rs-client { + base bgp-role; + description + "The local AS is a client of an RS and the RS is the + remote AS."; + } + + identity peer { + base bgp-role; + description + "The local and remote ASes have a peering relationship."; + } + + /****************************Typedefs************************/ + typedef predefined-next-hop { + type identityref { + base local-defined-next-hop; + } + description + "Predefined next-hop designation for locally generated + routes."; + } + + typedef area-address { + type string { + pattern '[0-9A-Fa-f]{2}(\.[0-9A-Fa-f]{4}){0,6}'; + } + description + "This type defines the area address format."; + } + + /************************Reusable groupings********************/ + /**** Service Status ****/ + + grouping service-status { + description + "Service status grouping."; + container status { + description + "Service status."; + container admin-status { + description + "Administrative service status."; + leaf status { + type identityref { + base vpn-common:administrative-status; + } + description + "Administrative service status."; + } + leaf last-change { + type yang:date-and-time; + config false; + description + "Indicates the actual date and time of the service + status change."; + } + } + container oper-status { + config false; + description + "Operational service status."; + uses vpn-common:oper-status-timestamp; + } + } + } + + /**** A set of profiles ****/ + + grouping ac-profile-cfg { + description + "Grouping for AC profile configuration."; + container valid-provider-identifiers { + description + "Container for valid provider profile identifiers. + The profiles only have significance within the service + provider's administrative domain."; + list encryption-profile-identifier { + key "id"; + description + "List of encryption profile identifiers."; + leaf id { + type string; + description + "Identification of the encryption profile to be used."; + } + } + list qos-profile-identifier { + key "id"; + description + "List of QoS profile identifiers."; + leaf id { + type string; + description + "Identification of the QoS profile to be used."; + } + } + list failure-detection-profile-identifier { + key "id"; + description + "List of BFD profile identifiers."; + leaf id { + type string; + description + "Identification of the a failure detection (e.g., BFD) + profile to be used."; + } + } + list forwarding-profile-identifier { + key "id"; + description + "List of forwarding profile identifiers."; + leaf id { + type string; + description + "Identification of the forwarding profile to be used."; + } + } + list routing-profile-identifier { + key "id"; + description + "List of routing profile identifiers."; + leaf id { + type string; + description + "Identification of the routing profile to be used by + the routing protocols over an AC."; + } + } + nacm:default-deny-write; + } + } + + /**** Operational instructions ****/ + + grouping op-instructions { + description + "Scheduling instructions."; + leaf requested-start { + type yang:date-and-time; + description + "Indicates the requested date and time when the service is + expected to be active."; + } + leaf requested-stop { + type yang:date-and-time; + description + "Indicates the requested date and time when the service is + expected to be disabled."; + } + leaf actual-start { + type yang:date-and-time; + config false; + description + "Indicates the actual date and time when the service + actually was enabled."; + } + leaf actual-stop { + type yang:date-and-time; + config false; + description + "Indicates the actual date and time when the service + actually was disabled."; + } + } + + /**** Layer 2 encapsulations ****/ + // Dot1q + + grouping dot1q { + description + "Defines a grouping for tagged interfaces."; + leaf tag-type { + type identityref { + base vpn-common:tag-type; + } + description + "Tag type."; + } + leaf cvlan-id { + type uint16 { + range "1..4094"; + } + description + "VLAN identifier."; + } + } + + // priority-tagged + + grouping priority-tagged { + description + "Priority tagged."; + leaf tag-type { + type identityref { + base vpn-common:tag-type; + } + description + "Tag type."; + } + } + + // QinQ + + grouping qinq { + description + "Includes QinQ parameters."; + leaf tag-type { + type identityref { + base vpn-common:tag-type; + } + description + "Tag type."; + } + leaf svlan-id { + type uint16 { + range "1..4094"; + } + description + "Service VLAN (S-VLAN) identifier."; + } + leaf cvlan-id { + type uint16 { + range "1..4094"; + } + description + "Customer VLAN (C-VLAN) identifier."; + } + } + + /**** Layer 2 tunnel services ****/ + // pseudowire (PW) + + grouping pseudowire { + description + "Includes pseudowire termination parameters."; + leaf vcid { + type uint32; + description + "Indicates a PW or virtual circuit (VC) identifier."; + } + leaf far-end { + type union { + type uint32; + type inet:ip-address; + } + description + "Neighbor reference."; + reference + "RFC 8077: Pseudowire Setup and Maintenance Using the Label + Distribution Protocol (LDP), Section 6.1"; + } + } + // VPLS + + grouping vpls { + description + "VPLS termination parameters."; + leaf vcid { + type uint32; + description + "VC identifier."; + } + leaf-list far-end { + type union { + type uint32; + type inet:ip-address; + } + description + "Neighbor reference."; + } + } + + // VXLAN + + grouping vxlan { + description + "VXLAN termination parameters."; + leaf vni-id { + type uint32; + description + "VXLAN Network Identifier (VNI)."; + } + leaf peer-mode { + type identityref { + base vpn-common:vxlan-peer-mode; + } + description + "Specifies the VXLAN access mode. By default, + the peer mode is set to 'static-mode'."; + } + leaf-list peer-ip-address { + type inet:ip-address; + description + "List of a peer's IP addresses."; + } + } + + // Layer 2 Tunnel service + + grouping l2-tunnel-service { + description + "Defines a Layer 2 tunnel termination."; + leaf type { + type identityref { + base l2-tunnel-type; + } + description + "Selects the tunnel termination type for an AC."; + } + container pseudowire { + when "derived-from-or-self(../type, 'ac-common:pseudowire')" { + description + "Only applies when the Layer 2 service type is + 'pseudowire'."; + } + description + "Includes pseudowire termination parameters."; + uses pseudowire; + } + container vpls { + when "derived-from-or-self(../type, 'ac-common:vpls')" { + description + "Only applies when the Layer 2 service type is 'vpls'."; + } + description + "VPLS termination parameters."; + uses vpls; + } + container vxlan { + when "derived-from-or-self(../type, 'ac-common:vxlan')" { + description + "Only applies when the Layer 2 service type is 'vxlan'."; + } + description + "VXLAN termination parameters."; + uses vxlan; + } + } + + /**** Layer 3 connection *****/ + // IPv4 allocation type + + grouping ipv4-allocation-type { + description + "IPv4-specific parameters."; + leaf prefix-length { + type uint8 { + range "0..32"; + } + description + "Subnet prefix length expressed in bits. It is applied to + both local and customer addresses."; + } + leaf address-allocation-type { + type identityref { + base address-allocation-type; + } + must "not(derived-from-or-self(current(), 'ac-common:slaac') " + + "or derived-from-or-self(current(), " + + "'ac-common:provider-dhcp-slaac'))" { + error-message "SLAAC is only applicable to IPv6."; + } + description + "Defines how IPv4 addresses are allocated to the peer site."; + } + } + + // IPv6 allocation type + + grouping ipv6-allocation-type { + description + "IPv6-specific parameters."; + leaf prefix-length { + type uint8 { + range "0..128"; + } + description + "Subnet prefix length expressed in bits. It is applied to + both local and customer addresses."; + } + leaf address-allocation-type { + type identityref { + base address-allocation-type; + } + description + "Defines how IPv6 addresses are allocated to the peer site."; + } + } + + // Basic parameters for IPv4 connection + + grouping ipv4-connection-basic { + description + "Basic set fof IPv4-specific parameters for the connection."; + uses ipv4-allocation-type; + choice allocation-type { + description + "Choice of the IPv4 address allocation."; + case dynamic { + description + "When the addresses are allocated by DHCP or other dynamic + means local to the infrastructure."; + choice provider-dhcp { + description + "Parameters related to DHCP-allocated addresses. IP + addresses are allocated by DHCP, that is provided by + the operator."; + leaf dhcp-service-type { + type enumeration { + enum server { + description + "Local DHCP server."; + } + enum relay { + description + "Local DHCP relay. DHCP requests are relayed to + a provider's server."; + } + } + description + "Indicates the type of DHCP service to be enabled on + an AC."; + } + } + choice dhcp-relay { + description + "The DHCP relay is provided by the operator."; + container customer-dhcp-servers { + description + "Container for a list of the customer's DHCP servers."; + leaf-list server-ip-address { + type inet:ipv4-address; + description + "IPv4 addresses of the customer's DHCP server."; + } + } + } + } + } + } + + // Basic parameters for IPv6 connection + + grouping ipv6-connection-basic { + description + "Basic set fof IPv6-specific parameters for the connection."; + uses ipv6-allocation-type; + choice allocation-type { + description + "Choice of the IPv6 address allocation."; + case dynamic { + description + "When the addresses are allocated by DHCP or other dynamic + means local to the infrastructure."; + choice provider-dhcp { + description + "Parameters related to DHCP-allocated addresses. + IP addresses are allocated by DHCP, that is provided + by the operator."; + leaf dhcp-service-type { + type enumeration { + enum server { + description + "Local DHCP server."; + } + enum relay { + description + "Local DHCP relay. DHCP requests are relayed to a + provider's server."; + } + } + description + "Indicates the type of DHCP service to be enabled on + the AC."; + } + } + choice dhcp-relay { + description + "The DHCP relay is provided by the operator."; + container customer-dhcp-servers { + description + "Container for a list of the customer's DHCP servers."; + leaf-list server-ip-address { + type inet:ipv6-address; + description + "IPv6 addresses of the customer's DHCP server."; + } + } + } + } + } + } + // Full parameters for the IPv4 connection + + grouping ipv4-connection { + description + "IPv4-specific parameters."; + leaf local-address { + type inet:ipv4-address; + description + "The IP address used at the provider's interface."; + } + leaf virtual-address { + type inet:ipv4-address; + description + "This addresss may be used for redundancy purposes."; + } + uses ipv4-allocation-type; + choice allocation-type { + description + "Choice of the IPv4 address allocation."; + case dynamic { + description + "When the addresses are allocated by DHCP or other + dynamic means local to the infrastructure."; + choice address-assign { + description + "A choice for how IPv4 addresses are assigned."; + case number { + leaf number-of-dynamic-address { + type uint16; + description + "Specifies the number of IP addresses to be assigned + to the customer on the AC."; + } + } + case explicit { + container customer-addresses { + description + "Container for customer addresses to be allocated + using DHCP."; + list address-pool { + key "pool-id"; + description + "Describes IP addresses to be dyncamically + allocated. + + When only 'start-address' is present, it + represents a single address. + + When both 'start-address' and 'end-address' are + specified, it implies a range inclusive of both + addresses."; + leaf pool-id { + type string; + description + "A pool identifier for the address range from + 'start-address' to 'end-address'."; + } + leaf start-address { + type inet:ipv4-address; + mandatory true; + description + "Indicates the first address in the pool."; + } + leaf end-address { + type inet:ipv4-address; + description + "Indicates the last address in the pool."; + } + } + } + } + } + choice provider-dhcp { + description + "Parameters related to DHCP-allocated addresses. IP + addresses are allocated by DHCP, which is provided by + the operator."; + leaf dhcp-service-type { + type enumeration { + enum server { + description + "Local DHCP server."; + } + enum relay { + description + "Local DHCP relay. DHCP requests are relayed to + a provider's server."; + } + } + description + "Indicates the type of DHCP service to be enabled on + this AC."; + } + } + choice dhcp-relay { + description + "The DHCP relay is provided by the operator."; + container customer-dhcp-servers { + description + "Container for a list of the customer's DHCP servers."; + leaf-list server-ip-address { + type inet:ipv4-address; + description + "IPv4 addresses of the customer's DHCP server."; + } + } + } + } + case static-addresses { + description + "Lists the IPv4 addresses that are used."; + list address { + key "address-id"; + ordered-by user; + description + "Lists the IPv4 addresses that are used. The first + address of the list is the primary address of the + connection."; + leaf address-id { + type string; + description + "An identifier of the static IPv4 address."; + } + leaf customer-address { + type inet:ipv4-address; + description + "An IPv4 address of the customer side."; + } + } + } + } + } + + // Full parameters for the IPv6 connection + + grouping ipv6-connection { + description + "IPv6-specific parameters."; + leaf local-address { + type inet:ipv6-address; + description + "IPv6 address of the provider side."; + } + leaf virtual-address { + type inet:ipv6-address; + description + "This addresss may be used for redundancy purposes."; + } + uses ipv6-allocation-type; + choice allocation-type { + description + "Choice of the IPv6 address allocation."; + case dynamic { + description + "When the addresses are allocated by DHCP or other + dynamic means local to the infrastructure."; + choice address-assign { + description + "A choice for how IPv6 addresses are assigned."; + case number { + leaf number-of-dynamic-address { + type uint16; + description + "Specifies the number of IP addresses to be + assigned to the customer on this access."; + } + } + case explicit { + container customer-addresses { + description + "Container for customer addresses to be allocated + using DHCP."; + list address-pool { + key "pool-id"; + description + "Describes IP addresses to be dyncamically + allocated. + + When only 'start-address' is present, it + represents a single address. + + When both 'start-address' and 'end-address' are + specified, it implies a range inclusive of both + addresses."; + leaf pool-id { + type string; + description + "A pool identifier for the address range from + 'start-address' to 'end-address'."; + } + leaf start-address { + type inet:ipv6-address; + mandatory true; + description + "Indicates the first address in the pool."; + } + leaf end-address { + type inet:ipv6-address; + description + "Indicates the last address in the pool."; + } + } + } + } + } + choice provider-dhcp { + description + "Parameters related to DHCP-allocated addresses. + IP addresses are allocated by DHCP, which is provided + by the operator."; + leaf dhcp-service-type { + type enumeration { + enum server { + description + "Local DHCP server."; + } + enum relay { + description + "Local DHCP relay. DHCP requests are relayed + to a provider's server."; + } + } + description + "Indicates the type of DHCP service to + be enabled on this access."; + } + } + choice dhcp-relay { + description + "The DHCP relay is provided by the operator."; + container customer-dhcp-servers { + description + "Container for a list of the customer's DHCP servers."; + leaf-list server-ip-address { + type inet:ipv6-address; + description + "IPv6 addresses of the customer's DHCP server."; + } + } + } + } + case static-addresses { + description + "Lists the IPv6 addresses that are used."; + list address { + key "address-id"; + ordered-by user; + description + "Lists the IPv6 addresses that are used. The first + address of the list is the primary IP address of + the connection."; + leaf address-id { + type string; + description + "An identifier of the static IPv6 address."; + } + leaf customer-address { + type inet:ipv6-address; + description + "An IPv6 address of the customer side."; + } + } + } + } + } + + /**** Routing ****/ + // Routing authentication + + grouping bgp-authentication { + description + "Grouping for BGP authentication parameters."; + container authentication { + description + "Container for BGP authentication parameters."; + leaf enabled { + type boolean; + description + "Enables or disables authentication."; + } + container keying-material { + when "../enabled = 'true'"; + description + "Container for describing how a BGP routing session is to + be secured on an AC."; + choice option { + description + "Choice of authentication options."; + case ao { + description + "Uses the TCP Authentication Option (TCP-AO)."; + reference + "RFC 5925: The TCP Authentication Option"; + leaf enable-ao { + type boolean; + description + "Enables the TCP-AO."; + } + leaf ao-keychain { + type key-chain:key-chain-ref; + description + "Reference to the TCP-AO key chain."; + reference + "RFC 8177: YANG Data Model for Key Chains"; + } + } + case md5 { + description + "Uses MD5 to secure the session."; + reference + "RFC 4364: BGP/MPLS IP Virtual Private Networks + (VPNs), Section 13.2"; + leaf md5-keychain { + type key-chain:key-chain-ref; + description + "Reference to the MD5 key chain."; + reference + "RFC 8177: YANG Data Model for Key Chains"; + } + } + case explicit { + leaf key-id { + type uint32; + description + "Key identifier."; + } + leaf key { + type string; + description + "BGP authentication key. + + This model only supports the subset of keys that + are representable as ASCII strings."; + } + leaf crypto-algorithm { + type identityref { + base key-chain:crypto-algorithm; + } + description + "Indicates the cryptographic algorithm associated + with the key."; + } + } + } + } + } + } + + grouping ospf-authentication { + description + "Authentication configuration."; + container authentication { + description + "Container for OSPF authentication parameters."; + leaf enabled { + type boolean; + description + "Enables or disables authentication."; + } + container keying-material { + when "../enabled = 'true'"; + description + "Container for describing how an OSPF session is to be + secured for this AC."; + choice option { + description + "Options for OSPF authentication."; + case auth-key-chain { + leaf key-chain { + type key-chain:key-chain-ref; + description + "Name of the key chain."; + } + } + case auth-key-explicit { + leaf key-id { + type uint32; + description + "Key identifier."; + } + leaf key { + type string; + description + "OSPF authentication key. + This model only supports the subset of keys that + are representable as ASCII strings."; + } + leaf crypto-algorithm { + type identityref { + base key-chain:crypto-algorithm; + } + description + "Indicates the cryptographic algorithm associated + with the key."; + } + } + } + } + } + } + + grouping isis-authentication { + description + "IS-IS authentication configuration."; + container authentication { + description + "Container for IS-IS authentication parameters."; + leaf enabled { + type boolean; + description + "Enables or disables authentication."; + } + container keying-material { + when "../enabled = 'true'"; + description + "Container for describing how an IS-IS session is secured + over an AC."; + choice option { + description + "Options for IS-IS authentication."; + case auth-key-chain { + leaf key-chain { + type key-chain:key-chain-ref; + description + "Name of the key chain."; + } + } + case auth-key-explicit { + leaf key-id { + type uint32; + description + "Key identifier."; + } + leaf key { + type string; + description + "IS-IS authentication key. + + This model only supports the subset of keys that + are representable as ASCII strings."; + } + leaf crypto-algorithm { + type identityref { + base key-chain:crypto-algorithm; + } + description + "Indicates the cryptographic algorithm associated + with the key."; + } + } + } + } + } + } + + grouping rip-authentication { + description + "RIP authentication configuration."; + container authentication { + description + "Container for RIP authentication parameters."; + leaf enabled { + type boolean; + description + "Enables or disables authentication."; + } + container keying-material { + when "../enabled = 'true'"; + description + "Container for describing how a RIP session is to be + secured on this AC."; + choice option { + description + "Specifies the authentication + scheme."; + case auth-key-chain { + leaf key-chain { + type key-chain:key-chain-ref; + description + "Name of the key chain."; + } + } + case auth-key-explicit { + leaf key { + type string; + description + "RIP authentication key. + + This model only supports the subset of keys that + are representable as ASCII strings."; + } + leaf crypto-algorithm { + type identityref { + base key-chain:crypto-algorithm; + } + description + "Indicates the cryptographic algorithm associated + with the key."; + } + } + } + } + } + } + + // Basic routing parameters + + grouping bgp-peer-group-without-name { + description + "Identifies a BGP peer-group configured on the local system."; + leaf local-as { + type inet:as-number; + description + "Indicates a local AS Number (ASN). This ASN is exposed + to a customer so that it knows which ASN to use + to set up a BGP session."; + } + leaf peer-as { + type inet:as-number; + description + "Indicates the customer's ASN when the customer + requests BGP routing."; + } + leaf address-family { + type identityref { + base vpn-common:address-family; + } + description + "This node contains the address families to be activated. + 'dual-stack' means that both IPv4 and IPv6 will be + activated."; + } + leaf role { + type identityref { + base ac-common:bgp-role; + } + description + "Specifies the BGP role (provider, customer, peer, etc.)."; + reference + "RFC 9234: Route Leak Prevention and Detection Using + Roles in UPDATE and OPEN Messages, Section 4"; + } + } + + grouping bgp-peer-group-with-name { + description + "Identifies a BGP peer-group configured on the local system - + identified by a peer-group name."; + leaf name { + type string; + description + "Name of the BGP peer-group."; + } + uses bgp-peer-group-without-name; + } + + grouping ospf-basic { + description + "Configuration specific to OSPF."; + leaf address-family { + type identityref { + base vpn-common:address-family; + } + description + "Indicates whether IPv4, IPv6, or both are to be activated."; + } + leaf area-id { + type yang:dotted-quad; + mandatory true; + description + "Area ID."; + reference + "RFC 4577: OSPF as the Provider/Customer Edge Protocol + for BGP/MPLS IP Virtual Private Networks + (VPNs), Section 4.2.3 + RFC 6565: OSPFv3 as a Provider Edge to Customer Edge + (PE-CE) Routing Protocol, Section 4.2"; + } + leaf metric { + type uint16; + description + "Metric of the AC. It is used in the routing state + calculation and path selection."; + } + } + + grouping isis-basic { + description + "Basic configuration specific to IS-IS."; + leaf address-family { + type identityref { + base vpn-common:address-family; + } + description + "Indicates whether IPv4, IPv6, or both are to be activated."; + } + leaf area-address { + type area-address; + mandatory true; + description + "Area address."; + } + } + + // Static routing + + grouping ipv4-static-rtg-entry { + description + "Paramters to configure a specific IPv4 static routing entry."; + leaf lan { + type inet:ipv4-prefix; + description + "LAN prefix."; + } + leaf lan-tag { + type string; + description + "Internal tag to be used in service policies."; + } + leaf next-hop { + type union { + type inet:ip-address; + type predefined-next-hop; + } + description + "The next hop that is to be used for the static route. + This may be specified as an IP address or a + predefined next-hop type (e.g., 'discard' or + 'local-link')."; + } + leaf metric { + type uint32; + description + "Indicates the metric associated with the static route."; + } + } + + grouping ipv4-static-rtg { + description + "Configuration specific to IPv4 static routing."; + list ipv4-lan-prefixes { + if-feature "vpn-common:ipv4"; + key "lan next-hop"; + description + "List of LAN prefixes for the site."; + uses ipv4-static-rtg-entry; + uses ac-common:service-status; + } + } + + grouping ipv6-static-rtg-entry { + description + "Paramters to configure a specific IPv6 static routing entry."; + leaf lan { + type inet:ipv6-prefix; + description + "LAN prefixes."; + } + leaf lan-tag { + type string; + description + "Internal tag to be used in service (e.g., VPN) policies."; + } + leaf next-hop { + type union { + type inet:ip-address; + type predefined-next-hop; + } + description + "The next hop that is to be used for the static route. + This may be specified as an IP address or a predefined + next-hop type (e.g., 'discard' or 'local-link')."; + } + leaf metric { + type uint32; + description + "Indicates the metric associated with the static route."; + } + } + + grouping ipv6-static-rtg { + description + "Configuration specific to IPv6 static routing."; + list ipv6-lan-prefixes { + if-feature "vpn-common:ipv6"; + key "lan next-hop"; + description + "List of LAN prefixes for the site."; + uses ipv6-static-rtg-entry; + uses ac-common:service-status; + } + } + + // OAM + + grouping bfd { + description + "A grouping for basic BFD."; + leaf holdtime { + type uint32; + units "milliseconds"; + description + "Expected BFD holdtime. + The customer may impose some fixed values + for the holdtime period if the provider allows + the customer to use this function. + If the provider doesn't allow the customer to + use this function, fixed values will not be set."; + reference + "RFC 5880: Bidirectional Forwarding Detection (BFD), + Section 6.8.18"; + } + } + + // redundancy + + grouping redundancy-group { + description + "A grouping for redundancy group."; + list group { + key "group-id"; + description + "List of group-ids."; + leaf group-id { + type string; + description + "Indicates the group-id to which the AC belongs."; + } + leaf precedence { + type identityref { + base ac-common:precedence-type; + } + description + "Defines redundancy of an AC."; + } + } + } + + // QoS + + grouping bandwidth-parameters { + description + "A grouping for bandwidth parameters."; + leaf cir { + type uint64; + units "bps"; + description + "Committed Information Rate (CIR). The maximum number of bits + that a port can receive or send during one second over + an interface."; + } + leaf cbs { + type uint64; + units "bytes"; + description + "Committed Burst Size (CBS). CBS controls the bursty nature + of the traffic. Traffic that does not use the configured + CIR accumulates credits until the credits reach the + configured CBS."; + } + leaf eir { + type uint64; + units "bps"; + description + "Excess Information Rate (EIR), i.e., excess frame delivery + allowed not subject to a Service Level Agreement (SLA). + The traffic rate can be limited by EIR."; + } + leaf ebs { + type uint64; + units "bytes"; + description + "Excess Burst Size (EBS). The bandwidth available for burst + traffic from the EBS is subject to the amount of bandwidth + that is accumulated during periods when traffic allocated + by the EIR policy is not used."; + } + leaf pir { + type uint64; + units "bps"; + description + "Peak Information Rate (PIR), i.e., maximum frame delivery + allowed. It is equal to or less than sum of CIR and EIR."; + } + leaf pbs { + type uint64; + units "bytes"; + description + "Peak Burst Size (PBS)."; + } + } + + grouping bandwidth-per-type { + description + "Grouping for bandwidth per type."; + list bandwidth { + key "bw-type"; + description + "List for bandwidth per type data nodes."; + leaf bw-type { + type identityref { + base vpn-common:bw-type; + } + description + "Indicates the bandwidth type."; + } + choice type { + description + "Choice based upon bandwidth type."; + case per-cos { + description + "Bandwidth per CoS."; + list cos { + key "cos-id"; + description + "List of Class of Services."; + leaf cos-id { + type uint8; + description + "Identifier of the CoS, indicated by a Differentiated + Services Code Point (DSCP) or a CE-CLAN CoS (802.1p) + value in the service frame."; + reference + "IEEE Std 802.1Q: Bridges and Bridged Networks"; + } + uses bandwidth-parameters; + } + } + case other { + description + "Other bandwidth types."; + uses bandwidth-parameters; + } + } + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-ac-svc@2024-08-06.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-ac-svc@2024-08-06.yang new file mode 100644 index 0000000000000000000000000000000000000000..a5790644f1f7600cfad42663f2e7c7ed13134ac1 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-ac-svc@2024-08-06.yang @@ -0,0 +1,1252 @@ +module ietf-ac-svc { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-ac-svc"; + prefix ac-svc; + + import ietf-ac-common { + prefix ac-common; + reference + "RFC CCCC: A Common YANG Data Model for Attachment Circuits"; + } + import ietf-vpn-common { + prefix vpn-common; + reference + "RFC 9181: A Common YANG Data Model for Layer 2 and Layer 3 + VPNs"; + } + import ietf-netconf-acm { + prefix nacm; + reference + "RFC 8341: Network Configuration Access Control Model"; + } + import ietf-inet-types { + prefix inet; + reference + "RFC 6991: Common YANG Data Types, Section 4"; + } + import ietf-key-chain { + prefix key-chain; + reference + "RFC 8177: YANG Data Model for Key Chains"; + } + + organization + "IETF OPSAWG (Operations and Management Area Working Group)"; + contact + "WG Web: <https://datatracker.ietf.org/wg/opsawg/> + WG List: <mailto:opsawg@ietf.org> + + Editor: Mohamed Boucadair + <mailto:mohamed.boucadair@orange.com> + Author: Richard Roberts + <mailto:rroberts@juniper.net> + Author: Oscar Gonzalez de Dios + <mailto:oscar.gonzalezdedios@telefonica.com> + Author: Samier Barguil + <mailto:ssamier.barguil_giraldo@nokia.com> + Author: Bo Wu + <mailto:lana.wubo@huawei.com>"; + description + "This YANG module defines a YANG model for exposing + attachment circuits as a service (ACaaS). + + Copyright (c) 2024 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Revised BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC XXXX; see the + RFC itself for full legal notices."; + + revision 2024-08-06 { + description + "Initial revision."; + reference + "RFC XXXX: YANG Data Models for Bearers and 'Attachment + Circuits'-as-a-Service (ACaaS)"; + } + + /* A set of typedefs to ease referencing cross-modules */ + + typedef attachment-circuit-reference { + type leafref { + path "/ac-svc:attachment-circuits/ac-svc:ac/ac-svc:name"; + } + description + "Defines a reference to an attachment circuit that can be used + by other modules."; + } + + typedef ac-group-reference { + type leafref { + path "/ac-svc:attachment-circuits/ac-svc:ac-group-profile" + + "/ac-svc:name"; + } + description + "Defines a reference to an attachment circuit profile."; + } + + typedef encryption-profile-reference { + type leafref { + path + "/ac-svc:specific-provisioning-profiles" + + "/ac-svc:valid-provider-identifiers" + + "/ac-svc:encryption-profile-identifier/ac-svc:id"; + } + description + "Defines a reference to an encryption profile."; + } + + typedef qos-profile-reference { + type leafref { + path + "/ac-svc:specific-provisioning-profiles" + + "/ac-svc:valid-provider-identifiers" + + "/ac-svc:qos-profile-identifier/ac-svc:id"; + } + description + "Defines a reference to a QoS profile."; + } + + typedef failure-detection-profile-reference { + type leafref { + path + "/ac-svc:specific-provisioning-profiles" + + "/ac-svc:valid-provider-identifiers" + + "/ac-svc:failure-detection-profile-identifier" + + "/ac-svc:id"; + } + description + "Defines a reference to a BFD profile."; + } + + typedef forwarding-profile-reference { + type leafref { + path + "/ac-svc:specific-provisioning-profiles" + + "/ac-svc:valid-provider-identifiers" + + "/ac-svc:forwarding-profile-identifier/ac-svc:id"; + } + description + "Defines a reference to a forwarding profile."; + } + + typedef routing-profile-reference { + type leafref { + path + "/ac-svc:specific-provisioning-profiles" + + "/ac-svc:valid-provider-identifiers" + + "/ac-svc:routing-profile-identifier/ac-svc:id"; + } + description + "Defines a reference to a routing profile."; + } + + typedef service-profile-reference { + type leafref { + path + "/ac-svc:service-provisioning-profiles" + + "/ac-svc:service-profile-identifier" + + "/ac-svc:id"; + } + description + "Defines a reference to a service profile."; + } + + /******************** Reusable groupings ********************/ + // Basic Layer 2 connection + + grouping l2-connection-basic { + description + "Defines Layer 2 protocols and parameters that can be + factorized when provisioning Layer 2 connectivity + among multiple ACs."; + container encapsulation { + description + "Container for Layer 2 encapsulation."; + leaf type { + type identityref { + base vpn-common:encapsulation-type; + } + description + "Encapsulation type."; + } + container dot1q { + when "derived-from-or-self(../type, 'vpn-common:dot1q')" { + description + "Only applies when the type of the tagged interface + is 'dot1q'."; + } + description + "Tagged interface."; + uses ac-common:dot1q; + } + container qinq { + when "derived-from-or-self(../type, 'vpn-common:qinq')" { + description + "Only applies when the type of the tagged interface + is 'qinq'."; + } + description + "Includes QinQ parameters."; + uses ac-common:qinq; + } + } + } + + // Full Layer 2 connection + + grouping l2-connection { + description + "Defines Layer 2 protocols and parameters that are used to + enable AC connectivity."; + container encapsulation { + description + "Container for Layer 2 encapsulation."; + leaf type { + type identityref { + base vpn-common:encapsulation-type; + } + description + "Indicates the encapsulation type."; + } + container dot1q { + when "derived-from-or-self(../type, 'vpn-common:dot1q')" { + description + "Only applies when the type of the tagged interface + is 'dot1q'."; + } + description + "Tagged interface."; + uses ac-common:dot1q; + } + container priority-tagged { + when "derived-from-or-self(../type, " + + "'vpn-common:priority-tagged')" { + description + "Only applies when the type of the tagged interface is + 'priority-tagged'."; + } + description + "Priority-tagged interface."; + uses ac-common:priority-tagged; + } + container qinq { + when "derived-from-or-self(../type, 'vpn-common:qinq')" { + description + "Only applies when the type of the tagged interface + is 'qinq'."; + } + description + "Includes QinQ parameters."; + uses ac-common:qinq; + } + } + choice l2-service { + description + "The Layer 2 connectivity service can be provided by + indicating a pointer to an L2VPN or by specifying a + Layer 2 tunnel service."; + container l2-tunnel-service { + description + "Defines a Layer 2 tunnel termination. + It is only applicable when a tunnel is required."; + uses ac-common:l2-tunnel-service; + } + case l2vpn { + leaf l2vpn-id { + type vpn-common:vpn-id; + description + "Indicates the L2VPN service associated with an + Integrated Routing and Bridging (IRB) interface."; + } + } + } + leaf bearer-reference { + if-feature "ac-common:server-assigned-reference"; + type string; + description + "This is an internal reference for the service provider + to identify the bearer associated with this AC."; + } + } + + // Basic IP connection + + grouping ip-connection-basic { + description + "Defines basic IP connection parameters."; + container ipv4 { + if-feature "vpn-common:ipv4"; + description + "IPv4-specific parameters."; + uses ac-common:ipv4-connection-basic; + } + container ipv6 { + if-feature "vpn-common:ipv6"; + description + "IPv6-specific parameters."; + uses ac-common:ipv6-connection-basic; + } + } + + // Full IP connection + + grouping ip-connection { + description + "Defines IP connection parameters."; + container ipv4 { + if-feature "vpn-common:ipv4"; + description + "IPv4-specific parameters."; + uses ac-common:ipv4-connection { + augment ac-svc:allocation-type/static-addresses/address { + leaf failure-detection-profile { + if-feature "vpn-common:bfd"; + type failure-detection-profile-reference; + description + "Points to a failure detection profile."; + } + description + "Adds a failure detection profile."; + } + } + } + container ipv6 { + if-feature "vpn-common:ipv6"; + description + "IPv6-specific parameters."; + uses ac-common:ipv6-connection { + augment ac-svc:allocation-type/static-addresses/address { + leaf failure-detection-profile { + if-feature "vpn-common:bfd"; + type failure-detection-profile-reference; + description + "Points to a failure detection profile."; + } + description + "Adds a failure detection profile."; + } + } + } + choice l3-service { + description + "The Layer 3 connectivity service can be provided by + specifying a Layer 3 tunnel service."; + container l3-tunnel-service { + description + "Defines a Layer 3 tunnel termination. + It is only applicable when a tunnel is required."; + leaf type { + type identityref { + base ac-common:l3-tunnel-type; + } + description + "Selects the tunnel termination type for an AC."; + } + } + } + } + + // Routing protocol list + + grouping routing-protocol-list { + description + "List of routing protocols used on the AC."; + leaf type { + type identityref { + base vpn-common:routing-protocol-type; + } + description + "Type of routing protocol."; + } + list routing-profiles { + key "id"; + description + "Routing profiles."; + leaf id { + type routing-profile-reference; + description + "Reference to the routing profile to be used."; + } + leaf type { + type identityref { + base vpn-common:ie-type; + } + description + "Import, export, or both."; + } + } + } + + // Static routing with BFD + + grouping ipv4-static-rtg-with-bfd { + description + "Configuration specific to IPv4 static routing with + failure protection (e.g., BFD)."; + list ipv4-lan-prefix { + if-feature "vpn-common:ipv4"; + key "lan next-hop"; + description + "List of LAN prefixes for the site."; + uses ac-common:ipv4-static-rtg-entry; + leaf failure-detection-profile { + if-feature "vpn-common:bfd"; + type failure-detection-profile-reference; + description + "Points to a failure detection profile."; + } + uses ac-common:service-status; + } + } + + grouping ipv6-static-rtg-with-bfd { + description + "Configuration specific to IPv6 static routing with + failure protection (e.g., BFD)."; + list ipv6-lan-prefix { + if-feature "vpn-common:ipv6"; + key "lan next-hop"; + description + "List of LAN prefixes for the site."; + uses ac-common:ipv6-static-rtg-entry; + leaf failure-detection-profile { + if-feature "vpn-common:bfd"; + type failure-detection-profile-reference; + description + "Points to a failure detection profile."; + } + uses ac-common:service-status; + } + } + + // BGP Service + + grouping bgp-neighbor-without-name { + description + "A grouping with generic parameters for configuring a BGP + neighbor."; + leaf remote-address { + type inet:ip-address; + description + "The remote IP address of this entry's BGP peer. This is + a customer IP address. + + If this leaf is not present, this means that the primary + customer IP address is used as remote IP address."; + } + leaf local-address { + type inet:ip-address; + description + "The provider's IP address that will be used to establish + the BGP session."; + } + uses ac-common:bgp-peer-group-without-name; + container bgp-max-prefix { + description + "A container for the maximum number of BGP prefixes + allowed in the BGP session."; + leaf max-prefix { + type uint32; + description + "Indicates the maximum number of BGP prefixes allowed + in the BGP session. + + It allows control of how many prefixes can be received + from a neighbor."; + reference + "RFC 4271: A Border Gateway Protocol 4 (BGP-4), + Section 8.2.2"; + } + } + uses ac-common:bgp-authentication; + uses ac-common:op-instructions; + uses ac-common:service-status; + } + + grouping bgp-neighbor-with-name { + description + "A grouping with generic parameters for configuring a BGP + neighbor with an identifier."; + leaf id { + type string; + description + "An identifier that uniquely identifies a neighbor."; + } + uses ac-svc:bgp-neighbor-without-name; + } + + grouping bgp-neighbor-with-server-reference { + description + "A grouping with generic parameters for configuring a BGP + neighbor with a reference generated by the provider."; + leaf server-reference { + if-feature "ac-common:server-assigned-reference"; + type string; + config false; + description + "This is an internal reference for the service provider + to identify the BGP session."; + } + uses ac-svc:bgp-neighbor-without-name; + } + + grouping bgp-neighbor-with-name-server-reference { + description + "A grouping with generic parameters for configuring a BGP + neighbor with an identifier and a reference generated by + the provider."; + leaf id { + type string; + description + "An identifier that uniquely identifiers a neighbor."; + } + uses ac-svc:bgp-neighbor-with-server-reference; + } + + grouping bgp-svc { + description + "Configuration specific to BGP."; + container peer-groups { + description + "Configuration for BGP peer-groups"; + list peer-group { + key "name"; + description + "List of BGP peer-groups configured on the local + system - uniquely identified by peer-group + name."; + uses ac-common:bgp-peer-group-with-name; + leaf local-address { + type inet:ip-address; + description + "The provider's local IP address that will be used to + establish the BGP session."; + } + container bgp-max-prefix { + description + "A container for the maximum number of BGP prefixes + allowed in the BGP session."; + leaf max-prefix { + type uint32; + description + "Indicates the maximum number of BGP prefixes allowed + in the BGP session. + + It allows control of how many prefixes can be received + from a neighbor."; + reference + "RFC 4271: A Border Gateway Protocol 4 (BGP-4), + Section 8.2.2"; + } + } + uses ac-common:bgp-authentication; + } + } + list neighbor { + key "id"; + description + "List of BGP neighbors."; + uses ac-svc:bgp-neighbor-with-name-server-reference; + leaf peer-group { + type leafref { + path "../../peer-groups/peer-group/name"; + } + description + "The peer-group with which this neighbor is associated."; + } + leaf failure-detection-profile { + if-feature "vpn-common:bfd"; + type failure-detection-profile-reference; + description + "Points to a failure detection profile."; + } + } + } + + // OSPF Service + + grouping ospf-svc { + description + "Service configuration specific to OSPF."; + uses ac-common:ospf-basic; + uses ac-common:ospf-authentication; + uses ac-common:service-status; + } + + // IS-IS Service + + grouping isis-svc { + description + "Service configuration specific to IS-IS."; + uses ac-common:isis-basic; + uses ac-common:isis-authentication; + uses ac-common:service-status; + } + + // RIP Service + + grouping rip-svc { + description + "Service configuration specific to RIP routing."; + leaf address-family { + type identityref { + base vpn-common:address-family; + } + description + "Indicates whether IPv4, IPv6, or both address families + are to be activated."; + } + uses ac-common:rip-authentication; + uses ac-common:service-status; + } + + // VRRP Service + + grouping vrrp-svc { + description + "Service configuration specific to VRRP."; + reference + "RFC 9568: Virtual Router Redundancy Protocol (VRRP) + Version 3 for IPv4 and IPv6"; + leaf address-family { + type identityref { + base vpn-common:address-family; + } + description + "Indicates whether IPv4, IPv6, or both + address families are to be enabled."; + } + uses ac-common:service-status; + } + + // Basic routing parameters + + grouping routing-basic { + description + "Defines basic parameters for routing protocols."; + list routing-protocol { + key "id"; + description + "List of routing protocols used on the AC."; + leaf id { + type string; + description + "Unique identifier for the routing protocol."; + } + uses routing-protocol-list; + container bgp { + when + "derived-from-or-self(../type, 'vpn-common:bgp-routing')" { + description + "Only applies when the protocol is BGP."; + } + if-feature "vpn-common:rtg-bgp"; + description + "Configuration specific to BGP."; + container peer-groups { + description + "Configuration for BGP peer-groups"; + list peer-group { + key "name"; + description + "List of BGP peer-groups configured on the local + system - uniquely identified by peer-group + name."; + uses ac-common:bgp-peer-group-with-name; + } + } + } + container ospf { + when "derived-from-or-self(../type, " + + "'vpn-common:ospf-routing')" { + description + "Only applies when the protocol is OSPF."; + } + if-feature "vpn-common:rtg-ospf"; + description + "Configuration specific to OSPF."; + uses ac-common:ospf-basic; + } + container isis { + when "derived-from-or-self(../type, " + + "'vpn-common:isis-routing')" { + description + "Only applies when the protocol is IS-IS."; + } + if-feature "vpn-common:rtg-isis"; + description + "Configuration specific to IS-IS."; + uses ac-common:isis-basic; + } + container rip { + when "derived-from-or-self(../type, " + + "'vpn-common:rip-routing')" { + description + "Only applies when the protocol is RIP. + For IPv4, the model assumes that RIP + version 2 is used."; + } + if-feature "vpn-common:rtg-rip"; + description + "Configuration specific to RIP routing."; + leaf address-family { + type identityref { + base vpn-common:address-family; + } + description + "Indicates whether IPv4, IPv6, or both + address families are to be activated."; + } + } + container vrrp { + when "derived-from-or-self(../type, " + + "'vpn-common:vrrp-routing')" { + description + "Only applies when the protocol is the + Virtual Router Redundancy Protocol (VRRP)."; + } + if-feature "vpn-common:rtg-vrrp"; + description + "Configuration specific to VRRP."; + leaf address-family { + type identityref { + base vpn-common:address-family; + } + description + "Indicates whether IPv4, IPv6, or both address families + are to be enabled."; + } + } + } + } + + // Full routing parameters + + grouping routing { + description + "Defines routing protocols."; + list routing-protocol { + key "id"; + description + "List of routing protocols used on the AC."; + leaf id { + type string; + description + "Unique identifier for the routing protocol."; + } + uses routing-protocol-list; + container static { + when "derived-from-or-self(../type, " + + "'vpn-common:static-routing')" { + description + "Only applies when the protocol is static routing + protocol."; + } + description + "Configuration specific to static routing."; + container cascaded-lan-prefixes { + description + "LAN prefixes from the customer."; + uses ipv4-static-rtg-with-bfd; + uses ipv6-static-rtg-with-bfd; + } + } + container bgp { + when "derived-from-or-self(../type, " + + "'vpn-common:bgp-routing')" { + description + "Only applies when the protocol is BGP."; + } + if-feature "vpn-common:rtg-bgp"; + description + "Configuration specific to BGP."; + uses bgp-svc; + } + container ospf { + when "derived-from-or-self(../type, " + + "'vpn-common:ospf-routing')" { + description + "Only applies when the protocol is OSPF."; + } + if-feature "vpn-common:rtg-ospf"; + description + "Configuration specific to OSPF."; + uses ospf-svc; + } + container isis { + when "derived-from-or-self(../type, " + + "'vpn-common:isis-routing')" { + description + "Only applies when the protocol is IS-IS."; + } + if-feature "vpn-common:rtg-isis"; + description + "Configuration specific to IS-IS."; + uses isis-svc; + } + container rip { + when "derived-from-or-self(../type, " + + "'vpn-common:rip-routing')" { + description + "Only applies when the protocol is RIP. + For IPv4, the model assumes that RIP version 2 is + used."; + } + if-feature "vpn-common:rtg-rip"; + description + "Configuration specific to RIP routing."; + uses rip-svc; + } + container vrrp { + when "derived-from-or-self(../type, " + + "'vpn-common:vrrp-routing')" { + description + "Only applies when the protocol is the Virtual Router + Redundancy Protocol (VRRP)."; + } + if-feature "vpn-common:rtg-vrrp"; + description + "Configuration specific to VRRP."; + uses vrrp-svc; + } + } + } + + // Encryption choice + + grouping encryption-choice { + description + "Container for the encryption profile."; + choice profile { + description + "Choice for the encryption profile."; + case provider-profile { + leaf provider-profile { + type encryption-profile-reference; + description + "Reference to a provider encryption profile."; + } + } + case customer-profile { + leaf customer-key-chain { + type key-chain:key-chain-ref; + description + "Customer-supplied key chain."; + } + } + } + } + + // Basic security parameters + + grouping ac-security-basic { + description + "AC-specific security parameters."; + container encryption { + if-feature "vpn-common:encryption"; + description + "Container for AC security encryption."; + leaf enabled { + type boolean; + description + "If set to 'true', traffic encryption on the connection + is required. Otherwise, it is disabled."; + } + leaf layer { + when "../enabled = 'true'" { + description + "Included only when encryption is enabled."; + } + type enumeration { + enum layer2 { + description + "Encryption occurs at Layer 2."; + } + enum layer3 { + description + "Encryption occurs at Layer 3. + For example, IPsec may be used when a customer + requests Layer 3 encryption."; + } + } + description + "Indicates the layer on which encryption is applied."; + } + } + container encryption-profile { + when "../encryption/enabled = 'true'" { + description + "Indicates the layer on which encryption is enabled."; + } + description + "Container for the encryption profile."; + uses encryption-choice; + } + } + + // Bandwith parameters + + grouping bandwidth { + description + "Container for bandwidth."; + container svc-pe-to-ce-bandwidth { + if-feature "vpn-common:inbound-bw"; + description + "From the customer site's perspective, the inbound + bandwidth of the AC or download bandwidth from the + service provider to the site."; + uses ac-common:bandwidth-per-type; + } + container svc-ce-to-pe-bandwidth { + if-feature "vpn-common:outbound-bw"; + description + "From the customer site's perspective, the outbound + bandwidth of the AC or upload bandwidth from + the CE to the PE."; + uses ac-common:bandwidth-per-type; + } + } + + // Basic AC parameters + + grouping ac-basic { + description + "Grouping for basic parameters for an attachment circuit."; + leaf name { + type string; + description + "A name that uniquely identifies the AC."; + } + container l2-connection { + if-feature "ac-common:layer2-ac"; + description + "Defines Layer 2 protocols and parameters that are required + to enable AC connectivity."; + uses l2-connection-basic; + } + container ip-connection { + if-feature "ac-common:layer3-ac"; + description + "Defines IP connection parameters."; + uses ip-connection-basic; + } + container routing-protocols { + description + "Defines routing protocols."; + uses routing-basic; + } + container oam { + description + "Defines the Operations, Administration, and Maintenance + (OAM) mechanisms used."; + container bfd { + if-feature "vpn-common:bfd"; + description + "Container for BFD."; + uses ac-common:bfd; + } + } + container security { + description + "AC-specific security parameters."; + uses ac-security-basic; + } + container service { + description + "AC-specific bandwith parameters."; + leaf mtu { + type uint32; + units "bytes"; + description + "Layer 2 MTU."; + } + uses bandwidth; + } + } + + // Full AC parameters + + grouping ac { + description + "Grouping for an attachment circuit."; + leaf name { + type string; + description + "A name of the AC. Data models that need to reference + an attachment circuit should use + attachment-circuit-reference."; + } + leaf-list service-profile { + type service-profile-reference; + description + "A reference to a service profile."; + } + container l2-connection { + if-feature "ac-common:layer2-ac"; + description + "Defines Layer 2 protocols and parameters that are required + to enable AC connectivity."; + uses l2-connection; + } + container ip-connection { + if-feature "ac-common:layer3-ac"; + description + "Defines IP connection parameters."; + uses ip-connection; + } + container routing-protocols { + description + "Defines routing protocols."; + uses routing; + } + container oam { + description + "Defines the OAM mechanisms used."; + container bfd { + if-feature "vpn-common:bfd"; + description + "Container for BFD."; + list session { + key "id"; + description + "List of BFD sessions."; + leaf id { + type string; + description + "A unique identifer for the BFD session."; + } + leaf local-address { + type inet:ip-address; + description + "Provider's IP address of the BFD session."; + } + leaf remote-address { + type inet:ip-address; + description + "Customer's IP address of the BFD session."; + } + leaf profile { + type failure-detection-profile-reference; + description + "Points to a BFD profile."; + } + uses ac-common:bfd; + uses ac-common:service-status; + } + } + } + container security { + description + "AC-specific security parameters."; + uses ac-security-basic; + } + container service { + description + "AC-specific bandwith parameters."; + leaf mtu { + type uint32; + units "bytes"; + description + "Layer 2 MTU."; + } + uses bandwidth; + container qos { + if-feature "vpn-common:qos"; + description + "QoS configuration."; + container qos-profiles { + description + "QoS profile configuration."; + list qos-profile { + key "profile"; + description + "Points to a QoS profile."; + leaf profile { + type qos-profile-reference; + description + "QoS profile to be used."; + } + leaf direction { + type identityref { + base vpn-common:qos-profile-direction; + } + description + "The direction to which the QoS profile + is applied."; + } + } + } + } + container access-control-list { + description + "Container for the Access Control List (ACL)."; + container acl-profiles { + description + "ACL profile configuration."; + list acl-profile { + key "profile"; + description + "Points to an ACL profile."; + leaf profile { + type forwarding-profile-reference; + description + "Forwarding profile to be used."; + } + } + } + } + } + } + + // Parent and Child ACs + + grouping ac-hierarchy { + description + "Container for parent and child AC references."; + leaf-list parent-ref { + type ac-svc:attachment-circuit-reference; + description + "Specifies a parent AC that is inherited by an AC. + In contexts where dynamic terminating points are + bound to the same AC, a parent AC with stable + information is created with a set of child ACs + to track dynamic AC information."; + } + leaf-list child-ref { + type ac-svc:attachment-circuit-reference; + config false; + description + "Specifies a child AC that relies upon a parent AC."; + } + } + + /******************** Main AC containers ********************/ + + container specific-provisioning-profiles { + description + "Contains a set of valid profiles to reference for an AC."; + uses ac-common:ac-profile-cfg; + } + container service-provisioning-profiles { + description + "Contains a set of valid profiles to reference for an AC."; + list service-profile-identifier { + key "id"; + description + "List of generic service profile identifiers."; + leaf id { + type string; + description + "Identification of the service profile to be used. + The profile only has significance within the service + provider's administrative domain."; + } + } + nacm:default-deny-write; + } + container attachment-circuits { + description + "Main container for the attachment circuits."; + list ac-group-profile { + key "name"; + description + "Maintains a list of profiles that are shared among + a set of ACs."; + uses ac; + } + container placement-constraints { + description + "Diversity constraint type."; + uses vpn-common:placement-constraints; + } + leaf customer-name { + type string; + description + "Indicates the name of the customer that requested these + ACs."; + } + uses ac-common:op-instructions; + list ac { + key "name"; + description + "Global provisioning of attachment circuits."; + leaf customer-name { + type string; + description + "Indicates the name of the customer that requested this + AC."; + } + leaf description { + type string; + description + "Associates a description with an AC."; + } + leaf test-only { + type empty; + description + "When present, this indicates that this is a feasibility + check request. No resources are commited for such AC + requests."; + } + uses ac-common:op-instructions; + leaf role { + type identityref { + base ac-common:role; + } + description + "Indicates whether this AC is used as UNI, NNI, etc."; + } + leaf-list peer-sap-id { + type string; + description + "One or more peer SAPs can be indicated."; + } + leaf-list group-profile-ref { + type ac-group-reference; + description + "A reference to an AC profile."; + } + uses ac-hierarchy; + uses ac-common:redundancy-group; + list service-ref { + key "service-type service-id"; + config false; + description + "Reports the set of services that are bound to the AC."; + leaf service-type { + type identityref { + base vpn-common:service-type; + } + description + "Indicates the service type (e.g., L3VPN or Network Slice + Service)."; + reference + "RFC 9408: A YANG Network Data Model for Service + Attachment Points (SAPs), Section 5"; + } + leaf service-id { + type string; + description + "Indicates an identifier of a service instance + of a given type that uses the AC."; + } + } + leaf server-reference { + if-feature "ac-common:server-assigned-reference"; + type string; + config false; + description + "Reports an internal reference for the service provider + to identify the AC."; + } + uses ac; + } + } +} \ No newline at end of file diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-ethertypes@2019-03-04.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-ethertypes@2019-03-04.yang new file mode 100644 index 0000000000000000000000000000000000000000..fd055074aeba5c277bbefdce0b81ebd24d0d3551 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-ethertypes@2019-03-04.yang @@ -0,0 +1,381 @@ +module ietf-ethertypes { + namespace "urn:ietf:params:xml:ns:yang:ietf-ethertypes"; + prefix ethertypes; + + organization + "IETF NETMOD (Network Modeling) Working Group."; + + contact + "WG Web: <https://datatracker.ietf.org/wg/netmod/> + WG List: <mailto:netmod@ietf.org> + + Editor: Mahesh Jethanandani + <mjethanandani@gmail.com>"; + + description + "This module contains common definitions for the + Ethertype used by different modules. It is a + placeholder module, till such time that IEEE + starts a project to define these Ethertypes + and publishes a standard. + + At that time, this module can be deprecated. + + Copyright (c) 2019 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD + License set forth in Section 4.c of the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8519; see + the RFC itself for full legal notices."; + + revision 2019-03-04 { + description + "Initial revision."; + reference + "RFC 8519: YANG Data Model for Network Access Control + Lists (ACLs)."; + } + + typedef ethertype { + type union { + type uint16; + type enumeration { + enum ipv4 { + value 2048; + description + "Internet Protocol version 4 (IPv4) with a + hex value of 0x0800."; + reference + "RFC 791: Internet Protocol."; + } + enum arp { + value 2054; + description + "Address Resolution Protocol (ARP) with a + hex value of 0x0806."; + reference + "RFC 826: An Ethernet Address Resolution Protocol: Or + Converting Network Protocol Addresses to 48.bit + Ethernet Address for Transmission on Ethernet + Hardware."; + } + enum wlan { + value 2114; + description + "Wake-on-LAN. Hex value of 0x0842."; + } + enum trill { + value 8947; + description + "Transparent Interconnection of Lots of Links. + Hex value of 0x22F3."; + reference + "RFC 6325: Routing Bridges (RBridges): Base Protocol + Specification."; + } + enum srp { + value 8938; + description + "Stream Reservation Protocol. Hex value of + 0x22EA."; + reference + "IEEE 801.1Q-2011."; + } + enum decnet { + value 24579; + description + "DECnet Phase IV. Hex value of 0x6003."; + } + enum rarp { + value 32821; + description + "Reverse Address Resolution Protocol. + Hex value 0x8035."; + reference + "RFC 903: A Reverse Address Resolution Protocol."; + } + enum appletalk { + value 32923; + description + "Appletalk (Ethertalk). Hex value of 0x809B."; + } + enum aarp { + value 33011; + description + "Appletalk Address Resolution Protocol. Hex value + of 0x80F3."; + } + enum vlan { + value 33024; + description + "VLAN-tagged frame (IEEE 802.1Q) and Shortest Path + Bridging IEEE 802.1aq with Network-Network + Interface (NNI) compatibility. Hex value of + 0x8100."; + reference + "IEEE 802.1Q."; + } + enum ipx { + value 33079; + description + "Internetwork Packet Exchange (IPX). Hex value + of 0x8137."; + } + enum qnx { + value 33284; + description + "QNX Qnet. Hex value of 0x8204."; + } + enum ipv6 { + value 34525; + description + "Internet Protocol Version 6 (IPv6). Hex value + of 0x86DD."; + reference + "RFC 8200: Internet Protocol, Version 6 (IPv6) + Specification + RFC 8201: Path MTU Discovery for IP version 6."; + } + enum efc { + value 34824; + description + "Ethernet flow control using pause frames. + Hex value of 0x8808."; + reference + "IEEE 802.1Qbb."; + } + enum esp { + value 34825; + description + "Ethernet Slow Protocol. Hex value of 0x8809."; + reference + "IEEE 802.3-2015."; + } + enum cobranet { + value 34841; + description + "CobraNet. Hex value of 0x8819."; + } + enum mpls-unicast { + value 34887; + description + "Multiprotocol Label Switching (MPLS) unicast traffic. + Hex value of 0x8847."; + reference + "RFC 3031: Multiprotocol Label Switching Architecture."; + } + enum mpls-multicast { + value 34888; + description + "MPLS multicast traffic. Hex value of 0x8848."; + reference + "RFC 3031: Multiprotocol Label Switching Architecture."; + } + enum pppoe-discovery { + value 34915; + description + "Point-to-Point Protocol over Ethernet. Used during + the discovery process. Hex value of 0x8863."; + reference + "RFC 2516: A Method for Transmitting PPP Over Ethernet + (PPPoE)."; + } + enum pppoe-session { + value 34916; + description + "Point-to-Point Protocol over Ethernet. Used during + session stage. Hex value of 0x8864."; + reference + "RFC 2516: A Method for Transmitting PPP Over Ethernet + (PPPoE)."; + } + enum intel-ans { + value 34925; + description + "Intel Advanced Networking Services. Hex value of + 0x886D."; + } + enum jumbo-frames { + value 34928; + description + "Jumbo frames or Ethernet frames with more than + 1500 bytes of payload, up to 9000 bytes."; + } + enum homeplug { + value 34939; + description + "Family name for the various power line + communications. Hex value of 0x887B."; + } + enum eap { + value 34958; + description + "Ethernet Access Protocol (EAP) over LAN. Hex value + of 0x888E."; + reference + "IEEE 802.1X."; + } + enum profinet { + value 34962; + description + "PROcess FIeld Net (PROFINET). Hex value of 0x8892."; + } + enum hyperscsi { + value 34970; + description + "Small Computer System Interface (SCSI) over Ethernet. + Hex value of 0x889A."; + } + enum aoe { + value 34978; + description + "Advanced Technology Advancement (ATA) over Ethernet. + Hex value of 0x88A2."; + } + enum ethercat { + value 34980; + description + "Ethernet for Control Automation Technology (EtherCAT). + Hex value of 0x88A4."; + } + enum provider-bridging { + value 34984; + description + "Provider Bridging (802.1ad) and Shortest Path Bridging + (801.1aq). Hex value of 0x88A8."; + reference + "IEEE 802.1ad and IEEE 802.1aq)."; + } + enum ethernet-powerlink { + value 34987; + description + "Ethernet Powerlink. Hex value of 0x88AB."; + } + enum goose { + value 35000; + description + "Generic Object Oriented Substation Event (GOOSE). + Hex value of 0x88B8."; + reference + "IEC/ISO 8802-2 and 8802-3."; + } + enum gse { + value 35001; + description + "Generic Substation Events. Hex value of 88B9."; + reference + "IEC 61850."; + } + enum sv { + value 35002; + description + "Sampled Value Transmission. Hex value of 0x88BA."; + reference + "IEC 61850."; + } + enum lldp { + value 35020; + description + "Link Layer Discovery Protocol (LLDP). Hex value of + 0x88CC."; + reference + "IEEE 802.1AB."; + } + enum sercos { + value 35021; + description + "Sercos Interface. Hex value of 0x88CD."; + } + enum wsmp { + value 35036; + description + "WAVE Short Message Protocol (WSMP). Hex value of + 0x88DC."; + } + enum homeplug-av-mme { + value 35041; + description + "HomePlug AV Mobile Management Entity (MME). Hex value + of 88E1."; + } + enum mrp { + value 35043; + description + "Media Redundancy Protocol (MRP). Hex value of + 0x88E3."; + reference + "IEC 62439-2."; + } + enum macsec { + value 35045; + description + "MAC Security. Hex value of 0x88E5."; + reference + "IEEE 802.1AE."; + } + enum pbb { + value 35047; + description + "Provider Backbone Bridges (PBB). Hex value of + 0x88E7."; + reference + "IEEE 802.1ah."; + } + enum cfm { + value 35074; + description + "Connectivity Fault Management (CFM). Hex value of + 0x8902."; + reference + "IEEE 802.1ag."; + } + enum fcoe { + value 35078; + description + "Fiber Channel over Ethernet (FCoE). Hex value of + 0x8906."; + reference + "T11 FC-BB-5."; + } + enum fcoe-ip { + value 35092; + description + "FCoE Initialization Protocol. Hex value of 0x8914."; + } + enum roce { + value 35093; + description + "RDMA over Converged Ethernet (RoCE). Hex value of + 0x8915."; + } + enum tte { + value 35101; + description + "TTEthernet Protocol Control Frame (TTE). Hex value + of 0x891D."; + reference + "SAE AS6802."; + } + enum hsr { + value 35119; + description + "High-availability Seamless Redundancy (HSR). Hex + value of 0x892F."; + reference + "IEC 62439-3:2016."; + } + } + } + description + "The uint16 type placeholder is defined to enable + users to manage their own ethertypes not + covered by the module. Otherwise, the module contains + enum definitions for the more commonly used ethertypes."; + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-geo-location@2022-02-11.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-geo-location@2022-02-11.yang new file mode 100644 index 0000000000000000000000000000000000000000..b815446f819ac2dcba50e63e5781e8a1be9b59c6 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-geo-location@2022-02-11.yang @@ -0,0 +1,278 @@ +module ietf-geo-location { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-geo-location"; + prefix geo; + import ietf-yang-types { + prefix yang; + reference "RFC 6991: Common YANG Data Types"; + } + + organization + "IETF NETMOD Working Group (NETMOD)"; + contact + "WG Web: <https://datatracker.ietf.org/wg/netmod/> + WG List: <mailto:netmod@ietf.org> + + Editor: Christian Hopps + <mailto:chopps@chopps.org>"; + + description + "This module defines a grouping of a container object for + specifying a location on or around an astronomical object (e.g., + 'earth'). + + The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL + NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', + 'MAY', and 'OPTIONAL' in this document are to be interpreted as + described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, + they appear in all capitals, as shown here. + + Copyright (c) 2022 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, + with or without modification, is permitted pursuant to, + and subject to the license terms contained in, the + Revised BSD License set forth in Section 4.c of the + IETF Trust's Legal Provisions Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 9179 + (https://www.rfc-editor.org/info/rfc9179); see the RFC itself + for full legal notices."; + + revision 2022-02-11 { + description + "Initial Revision"; + reference + "RFC 9179: A YANG Grouping for Geographic Locations"; + } + + feature alternate-systems { + description + "This feature means the device supports specifying locations + using alternate systems for reference frames."; + } + + grouping geo-location { + description + "Grouping to identify a location on an astronomical object."; + + container geo-location { + description + "A location on an astronomical body (e.g., 'earth') + somewhere in a universe."; + + container reference-frame { + description + "The Frame of Reference for the location values."; + + leaf alternate-system { + if-feature "alternate-systems"; + type string; + description + "The system in which the astronomical body and + geodetic-datum is defined. Normally, this value is not + present and the system is the natural universe; however, + when present, this value allows for specifying alternate + systems (e.g., virtual realities). An alternate-system + modifies the definition (but not the type) of the other + values in the reference frame."; + } + leaf astronomical-body { + type string { + pattern '[ -@\[-\^_-~]*'; + } + default "earth"; + description + "An astronomical body as named by the International + Astronomical Union (IAU) or according to the alternate + system if specified. Examples include 'sun' (our star), + 'earth' (our planet), 'moon' (our moon), 'enceladus' (a + moon of Saturn), 'ceres' (an asteroid), and + '67p/churyumov-gerasimenko (a comet). The ASCII value + SHOULD have uppercase converted to lowercase and not + include control characters (i.e., values 32..64, and + 91..126). Any preceding 'the' in the name SHOULD NOT be + included."; + reference + "https://www.iau.org/"; + } + container geodetic-system { + description + "The geodetic system of the location data."; + leaf geodetic-datum { + type string { + pattern '[ -@\[-\^_-~]*'; + } + description + "A geodetic-datum defining the meaning of latitude, + longitude, and height. The default when the + astronomical body is 'earth' is 'wgs-84', which is + used by the Global Positioning System (GPS). The + ASCII value SHOULD have uppercase converted to + lowercase and not include control characters + (i.e., values 32..64, and 91..126). The IANA registry + further restricts the value by converting all spaces + (' ') to dashes ('-'). + The specification for the geodetic-datum indicates + how accurately it models the astronomical body in + question, both for the 'horizontal' + latitude/longitude coordinates and for height + coordinates."; + reference + "RFC 9179: A YANG Grouping for Geographic Locations, + Section 6.1"; + } + leaf coord-accuracy { + type decimal64 { + fraction-digits 6; + } + description + "The accuracy of the latitude/longitude pair for + ellipsoidal coordinates, or the X, Y, and Z components + for Cartesian coordinates. When coord-accuracy is + specified, it indicates how precisely the coordinates + in the associated list of locations have been + determined with respect to the coordinate system + defined by the geodetic-datum. For example, there + might be uncertainty due to measurement error if an + experimental measurement was made to determine each + location."; + } + leaf height-accuracy { + type decimal64 { + fraction-digits 6; + } + units "meters"; + description + "The accuracy of the height value for ellipsoidal + coordinates; this value is not used with Cartesian + coordinates. When height-accuracy is specified, it + indicates how precisely the heights in the + associated list of locations have been determined + with respect to the coordinate system defined by the + geodetic-datum. For example, there might be + uncertainty due to measurement error if an + experimental measurement was made to determine each + location."; + } + } + } + choice location { + description + "The location data either in latitude/longitude or + Cartesian values"; + case ellipsoid { + leaf latitude { + type decimal64 { + fraction-digits 16; + } + units "decimal degrees"; + description + "The latitude value on the astronomical body. The + definition and precision of this measurement is + indicated by the reference-frame."; + } + leaf longitude { + type decimal64 { + fraction-digits 16; + } + units "decimal degrees"; + description + "The longitude value on the astronomical body. The + definition and precision of this measurement is + indicated by the reference-frame."; + } + leaf height { + type decimal64 { + fraction-digits 6; + } + units "meters"; + description + "Height from a reference 0 value. The precision and + '0' value is defined by the reference-frame."; + } + } + case cartesian { + leaf x { + type decimal64 { + fraction-digits 6; + } + units "meters"; + description + "The X value as defined by the reference-frame."; + } + leaf y { + type decimal64 { + fraction-digits 6; + } + units "meters"; + description + "The Y value as defined by the reference-frame."; + } + leaf z { + type decimal64 { + fraction-digits 6; + } + units "meters"; + description + "The Z value as defined by the reference-frame."; + } + } + } + container velocity { + description + "If the object is in motion, the velocity vector describes + this motion at the time given by the timestamp. For a + formula to convert these values to speed and heading, see + RFC 9179."; + reference + "RFC 9179: A YANG Grouping for Geographic Locations"; + + leaf v-north { + type decimal64 { + fraction-digits 12; + } + units "meters per second"; + description + "v-north is the rate of change (i.e., speed) towards + true north as defined by the geodetic-system."; + } + + leaf v-east { + type decimal64 { + fraction-digits 12; + } + units "meters per second"; + description + "v-east is the rate of change (i.e., speed) perpendicular + to the right of true north as defined by + the geodetic-system."; + } + + leaf v-up { + type decimal64 { + fraction-digits 12; + } + units "meters per second"; + description + "v-up is the rate of change (i.e., speed) away from the + center of mass."; + } + } + leaf timestamp { + type yang:date-and-time; + description + "Reference time when location was recorded."; + } + leaf valid-until { + type yang:date-and-time; + description + "The timestamp for which this geo-location is valid until. + If unspecified, the geo-location has no specific + expiration time."; + } + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-inet-types@2024-10-21.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-inet-types@2024-10-21.yang new file mode 100644 index 0000000000000000000000000000000000000000..78c5201baf73f339d3ac409083b1f8ea13682faf --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-inet-types@2024-10-21.yang @@ -0,0 +1,657 @@ +module ietf-inet-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-inet-types"; + prefix "inet"; + + organization + "IETF Network Modeling (NETMOD) Working Group"; + + contact + "WG Web: <https://datatracker.ietf.org/wg/netmod/> + WG List: <mailto:netmod@ietf.org> + + Editor: Juergen Schoenwaelder + <mailto:jschoenwaelder@constructor.university>"; + + description + "This module contains a collection of generally useful derived + YANG data types for Internet addresses and related things. + + The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL + NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', + 'MAY', and 'OPTIONAL' in this document are to be interpreted as + described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, + they appear in all capitals, as shown here. + + Copyright (c) 2024 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Revised BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC XXXX; + see the RFC itself for full legal notices."; + + revision 2024-10-21 { + description + "This revision adds the following new data types: + - inet:ip-address-and-prefix + - inet:ipv4-address-and-prefix + - inet:ipv6-address-and-prefix + - inet:protocol-number + - inet:host-name + - inet:email-address + - inet:ip-address-link-local + - inet:ipv4-address-link-local + - inet:ipv6-address-link-local + The inet:host union was changed to use inet:host-name instead + of inet:domain-name. Several pattern statements have been + improved."; + reference + "RFC XXXX: Common YANG Data Types"; + } + + revision 2013-07-15 { + description + "This revision adds the following new data types: + - inet:ip-address-no-zone + - inet:ipv4-address-no-zone + - inet:ipv6-address-no-zone"; + reference + "RFC 6991: Common YANG Data Types"; + } + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + /*** collection of types related to protocol fields ***/ + + typedef ip-version { + type enumeration { + enum unknown { + value "0"; + description + "An unknown or unspecified version of the Internet + protocol."; + } + enum ipv4 { + value "1"; + description + "The IPv4 protocol as defined in RFC 791."; + } + enum ipv6 { + value "2"; + description + "The IPv6 protocol as defined in RFC 8200."; + } + } + description + "This value represents the version of the IP protocol. + + In the value set and its semantics, this type is equivalent + to the InetVersion textual convention of the SMIv2."; + reference + "RFC 791: Internet Protocol + RFC 8200: Internet Protocol, Version 6 (IPv6) Specification + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + typedef dscp { + type uint8 { + range "0..63"; + } + description + "The dscp type represents a Differentiated Services Code Point + that may be used for marking packets in a traffic stream. + + In the value set and its semantics, this type is equivalent + to the Dscp textual convention of the SMIv2."; + reference + "RFC 3289: Management Information Base for the Differentiated + Services Architecture + RFC 2474: Definition of the Differentiated Services Field + (DS Field) in the IPv4 and IPv6 Headers + RFC 2780: IANA Allocation Guidelines For Values In + the Internet Protocol and Related Headers"; + } + + typedef ipv6-flow-label { + type uint32 { + range "0..1048575"; + } + description + "The ipv6-flow-label type represents the flow identifier or + Flow Label in an IPv6 packet header that may be used to + discriminate traffic flows. + + In the value set and its semantics, this type is equivalent + to the IPv6FlowLabel textual convention of the SMIv2."; + reference + "RFC 3595: Textual Conventions for IPv6 Flow Label + RFC 8200: Internet Protocol, Version 6 (IPv6) Specification"; + } + + typedef port-number { + type uint16 { + range "0..65535"; + } + description + "The port-number type represents a 16-bit port number of an + Internet transport-layer protocol such as UDP, TCP, DCCP, or + SCTP. + + Port numbers are assigned by IANA. The current list of + all assignments is available from <https://www.iana.org/>. + + Note that the port number value zero is reserved by IANA. In + situations where the value zero does not make sense, it can + be excluded by subtyping the port-number type. + + In the value set and its semantics, this type is equivalent + to the InetPortNumber textual convention of the SMIv2."; + reference + "RFC 768: User Datagram Protocol + RFC 9293: Transmission Control Protocol (TCP) + RFC 9260: Stream Control Transmission Protocol + RFC 4340: Datagram Congestion Control Protocol (DCCP) + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + typedef protocol-number { + type uint8; + description + "The protocol-number type represents an 8-bit Internet + protocol number, carried in the 'protocol' field of the + IPv4 header or in the 'next header' field of the IPv6 + header. If IPv6 extension headers are present, then the + protocol number type represents the upper layer protocol + number, i.e., the number of the last 'next header' field + of the IPv6 extension headers. + + Protocol numbers are assigned by IANA. The current list of + all assignments is available from <https://www.iana.org/>."; + reference + "RFC 791: Internet Protocol + RFC 8200: Internet Protocol, Version 6 (IPv6) Specification"; + } + + /*** collection of types related to autonomous systems ***/ + + typedef as-number { + type uint32; + description + "The as-number type represents autonomous system numbers + which identify an Autonomous System (AS). An AS is a set + of routers under a single technical administration, using + an interior gateway protocol and common metrics to route + packets within the AS, and using an exterior gateway + protocol to route packets to other ASes. IANA maintains + the AS number space and has delegated large parts to the + regional registries. + + Autonomous system numbers were originally limited to 16 + bits. BGP extensions have enlarged the autonomous system + number space to 32 bits. This type therefore uses an uint32 + base type without a range restriction in order to support + a larger autonomous system number space. + + In the value set and its semantics, this type is equivalent + to the InetAutonomousSystemNumber textual convention of + the SMIv2."; + reference + "RFC 1930: Guidelines for creation, selection, and registration + of an Autonomous System (AS) + RFC 4271: A Border Gateway Protocol 4 (BGP-4) + RFC 4001: Textual Conventions for Internet Network Addresses + RFC 6793: BGP Support for Four-Octet Autonomous System (AS) + Number Space"; + } + + /*** collection of types related to IP addresses and hostnames ***/ + + typedef ip-address { + type union { + type ipv4-address; + type ipv6-address; + } + description + "The ip-address type represents an IP address and is IP + version neutral. The format of the textual representation + implies the IP version. This type supports scoped addresses + by allowing zone identifiers in the address format."; + reference + "RFC 4007: IPv6 Scoped Address Architecture"; + } + + typedef ipv4-address { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '(%.+)?'; + } + description + "The ipv4-address type represents an IPv4 address in + dotted-quad notation. The IPv4 address may include a zone + index, separated by a % sign. If a system uses zone names + that are not represented in UTF-8, then an implementation + needs to use some mechanism to transform the local name + into UTF-8. The definition of such a mechanism is outside + the scope of this document. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format for the zone index is the numerical + format"; + } + + typedef ipv6-address { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(%[A-Za-z0-9][A-Za-z0-9\-\._~/]*)?'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(%.+)?'; + } + description + "The ipv6-address type represents an IPv6 address in full, + mixed, shortened, and shortened-mixed notation. The IPv6 + address may include a zone index, separated by a % sign. + If a system uses zone names that are not represented in + UTF-8, then an implementation needs to use some mechanism + to transform the local name into UTF-8. The definition of + such a mechanism is outside the scope of this document. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format of IPv6 addresses uses the textual + representation defined in Section 4 of RFC 5952. The + canonical format for the zone index is the numerical + format as described in Section 11.2 of RFC 4007."; + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + typedef ip-address-no-zone { + type union { + type ipv4-address-no-zone; + type ipv6-address-no-zone; + } + description + "The ip-address-no-zone type represents an IP address and is + IP version neutral. The format of the textual representation + implies the IP version. This type does not support scoped + addresses since it does not allow zone identifiers in the + address format."; + reference + "RFC 4007: IPv6 Scoped Address Architecture"; + } + + typedef ipv4-address-no-zone { + type ipv4-address { + pattern '[0-9\.]*'; + } + description + "An IPv4 address without a zone index. This type, derived + from the type ipv4-address, may be used in situations where + the zone is known from the context and no zone index is + needed."; + } + + typedef ipv6-address-no-zone { + type ipv6-address { + pattern '[0-9a-fA-F:\.]*'; + } + description + "An IPv6 address without a zone index. This type, derived + from the type ipv6-address, may be used in situations where + the zone is known from the context and no zone index is + needed."; + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + typedef ip-address-link-local { + type union { + type ipv4-address-link-local; + type ipv6-address-link-local; + } + description + "The ip-address-link-local type represents a link-local IP + address and is IP version neutral. The format of the textual + representation implies the IP version."; + } + + typedef ipv4-address-link-local { + type ipv4-address { + pattern '169\.254\..*'; + } + description + "A link-local IPv4 address in the prefix 169.254.0.0/16 as + defined in section 2.1. of RFC 3927."; + reference + "RFC 3927: Dynamic Configuration of IPv4 Link-Local Addresses"; + } + + typedef ipv6-address-link-local { + type ipv6-address { + pattern '[fF][eE]80:.*'; + } + description + "A link-local IPv6 address in the prefix fe80::/10 as defined + in section 2.5.6. of RFC 4291."; + reference + "RFC 4291: IP Version 6 Addressing Architecture"; + } + + typedef ip-prefix { + type union { + type ipv4-prefix; + type ipv6-prefix; + } + description + "The ip-prefix type represents an IP prefix and is IP + version neutral. The format of the textual representations + implies the IP version."; + } + + typedef ipv4-prefix { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '/(([0-9])|([1-2][0-9])|(3[0-2]))'; + } + description + "The ipv4-prefix type represents an IPv4 prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 32. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The canonical format of an IPv4 prefix has all bits of + the IPv4 address set to zero that are not part of the + IPv4 prefix. + + The definition of ipv4-prefix does not require that bits, + which are not part of the prefix, are set to zero. However, + implementations have to return values in canonical format, + which requires non-prefix bits to be set to zero. This means + that 192.0.2.1/24 must be accepted as a valid value but it + will be converted into the canonical format 192.0.2.0/24."; + } + + typedef ipv6-prefix { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(/(([0-9])|([0-9]{2})|(1[0-1][0-9])|(12[0-8])))'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(/.+)'; + } + description + "The ipv6-prefix type represents an IPv6 prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 128. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The canonical format of an IPv6 prefix has all bits of + the IPv6 address set to zero that are not part of the + IPv6 prefix. Furthermore, the IPv6 address is represented + as defined in Section 4 of RFC 5952. + + The definition of ipv6-prefix does not require that bits, + which are not part of the prefix, are set to zero. However, + implementations have to return values in canonical format, + which requires non-prefix bits to be set to zero. This means + that 2001:db8::1/64 must be accepted as a valid value but it + will be converted into the canonical format 2001:db8::/64."; + reference + "RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + typedef ip-address-and-prefix { + type union { + type ipv4-address-and-prefix; + type ipv6-address-and-prefix; + } + description + "The ip-address-and-prefix type represents an IP address and + prefix and is IP version neutral. The format of the textual + representations implies the IP version."; + } + + typedef ipv4-address-and-prefix { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '/(([0-9])|([1-2][0-9])|(3[0-2]))'; + } + description + "The ipv4-address-and-prefix type represents an IPv4 + address and an associated IPv4 prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 32. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0."; + } + + typedef ipv6-address-and-prefix { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(/(([0-9])|([0-9]{2})|(1[0-1][0-9])|(12[0-8])))'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(/.+)'; + } + description + "The ipv6-address-and-prefix type represents an IPv6 + address and an associated IPv6 prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 128. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The canonical format requires that the IPv6 address is + represented as defined in Section 4 of RFC 5952."; + reference + "RFC 5952: A Recommendation for IPv6 Address Text + Representation"; + } + + /*** collection of domain name and URI types ***/ + + typedef domain-name { + type string { + length "1..253"; + pattern + '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*' + + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)' + + '|\.'; + } + description + "The domain-name type represents a DNS domain name. The + name SHOULD be fully qualified whenever possible. This + type does not support wildcards (see RFC 4592) or + classless in-addr.arpa delegations (see RFC 2317). + + Internet domain names are only loosely specified. Section + 3.5 of RFC 1034 recommends a syntax (modified in Section + 2.1 of RFC 1123). The pattern above is intended to allow + for current practice in domain name use, and some possible + future expansion. Note that Internet host names have a + stricter syntax (described in RFC 952) than the DNS + recommendations in RFCs 1034 and 1123. Schema nodes + representing host names should use the host-name type + instead of the domain-type. + + The encoding of DNS names in the DNS protocol is limited + to 255 characters. Since the encoding consists of labels + prefixed by a length bytes and there is a trailing NULL + byte, only 253 characters can appear in the textual dotted + notation. + + The description clause of schema nodes using the domain-name + type MUST describe when and how these names are resolved to + IP addresses. Note that the resolution of a domain-name value + may require to query multiple DNS records (e.g., A for IPv4 + and AAAA for IPv6). The order of the resolution process and + which DNS record takes precedence can either be defined + explicitly or may depend on the configuration of the + resolver. + + Domain-name values use the US-ASCII encoding. Their canonical + format uses lowercase US-ASCII characters. Internationalized + domain names MUST be A-labels as per RFC 5890."; + reference + "RFC 952: DoD Internet Host Table Specification + RFC 1034: Domain Names - Concepts and Facilities + RFC 1123: Requirements for Internet Hosts -- Application + and Support + RFC 2317: Classless IN-ADDR.ARPA delegation + RFC 2782: A DNS RR for specifying the location of services + (DNS SRV) + RFC 4592: The Role of Wildcards in the Domain Name System + RFC 5890: Internationalized Domain Names in Applications + (IDNA): Definitions and Document Framework + RFC 9499: DNS Terminology"; + } + + typedef host-name { + type domain-name { + length "2..max"; + pattern '[a-zA-Z0-9\-\.]+'; + } + description + "The host-name type represents (fully qualified) host names. + Host names must be at least two characters long (see RFC 952) + and they are restricted to labels consisting of letters, digits + and hyphens separated by dots (see RFC1123 and RFC 952)."; + reference + "RFC 952: DoD Internet Host Table Specification + RFC 1123: Requirements for Internet Hosts -- Application + and Support"; + } + + typedef host { + type union { + type ip-address; + type host-name; + } + description + "The host type represents either an IP address or a (fully + qualified) host name."; + } + + typedef uri { + type string { + pattern '[a-z][a-z0-9+.-]*:.*'; + } + description + "The uri type represents a Uniform Resource Identifier + (URI) as defined by the rule 'URI' in RFC 3986. + + Objects using the uri type MUST be in US-ASCII encoding, + and MUST be normalized as described by RFC 3986 Sections + 6.2.1, 6.2.2.1, and 6.2.2.2. All unnecessary + percent-encoding is removed, and all case-insensitive + characters are set to lowercase except for hexadecimal + digits within a percent-encoded triplet, which are + normalized to uppercase as described in Section 6.2.2.1 + of RFC 3986. + + The purpose of this normalization is to help provide + unique URIs. Note that this normalization is not + sufficient to provide uniqueness. Two URIs that are + textually distinct after this normalization may still be + equivalent. + + Objects using the uri type may restrict the schemes that + they permit. For example, 'data:' and 'urn:' schemes + might not be appropriate. + + A zero-length URI is not a valid URI. This can be used to + express 'URI absent' where required. + + In the value set and its semantics, this type is equivalent + to the Uri SMIv2 textual convention defined in RFC 5017."; + reference + "RFC 3986: Uniform Resource Identifier (URI): Generic Syntax + RFC 3305: Report from the Joint W3C/IETF URI Planning Interest + Group: Uniform Resource Identifiers (URIs), URLs, + and Uniform Resource Names (URNs): Clarifications + and Recommendations + RFC 5017: MIB Textual Conventions for Uniform Resource + Identifiers (URIs)"; + } + + typedef email-address { + type string { + pattern '.+@.+'; + } + description + "The email-address type represents an internationalized + email address. + + The email address format is defined by the addr-spec + ABNF rule in RFC 5322 section 3.4.1. This format has + been extended by RFC 6532 to support internationalized + email addresses. Implementations MUST support the + internationalization extensions of RFC 6532. Support + of the obsolete obs-local-part, obs-domain, and + obs-qtext parts of RFC 5322 is not required. + + The domain part may use both A-labels and U-labels + (see RFC 5890). The canonical format of the domain part + uses lowercase characters and U-labels (RFC 5890) where + applicable."; + reference + "RFC 5322: Internet Message Format + RFC 5890: Internationalized Domain Names in Applications + (IDNA): Definitions and Document Framework + RFC 6531: SMTP Extension for Internationalized Email"; + } + +} \ No newline at end of file diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-key-chain@2017-06-15.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-key-chain@2017-06-15.yang new file mode 100644 index 0000000000000000000000000000000000000000..445d1994a5ac57366078b198200a9e143d4ccda8 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-key-chain@2017-06-15.yang @@ -0,0 +1,382 @@ +module ietf-key-chain { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-key-chain"; + prefix key-chain; + + import ietf-yang-types { + prefix yang; + } + import ietf-netconf-acm { + prefix nacm; + } + + organization + "IETF RTGWG - Routing Area Working Group"; + contact + "WG Web: <https://datatracker.ietf.org/group/rtgwg> + WG List: <mailto:rtgwg@ietf.org> + + Editor: Acee Lindem + <mailto:acee@cisco.com> + Yingzhen Qu + <mailto:yingzhen.qu@huawei.com> + Derek Yeung + <mailto:derek@arrcus.com> + Ing-Wher Chen + <mailto:Ing-Wher_Chen@jabail.com> + Jeffrey Zhang + <mailto:zzhang@juniper.net>"; + + description + "This YANG module defines the generic configuration + data for key chains. It is intended that the module + will be extended by vendors to define vendor-specific + key chain configuration parameters. + + Copyright (c) 2017 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8177; + see the RFC itself for full legal notices."; + + reference "RFC 8177"; + + revision 2017-06-15 { + description + "Initial RFC Revision"; + reference "RFC 8177: YANG Data Model for Key Chains"; + } + + feature hex-key-string { + description + "Support hexadecimal key string."; + } + + feature accept-tolerance { + description + "Support the tolerance or acceptance limit."; + } + + feature independent-send-accept-lifetime { + description + "Support for independent send and accept key lifetimes."; + } + + feature crypto-hmac-sha-1-12 { + description + "Support for TCP HMAC-SHA-1 12-byte digest hack."; + } + + feature cleartext { + description + "Support for cleartext algorithm. Usage is + NOT RECOMMENDED."; + } + + feature aes-cmac-prf-128 { + description + "Support for AES Cipher-based Message Authentication + Code Pseudorandom Function."; + } + + feature aes-key-wrap { + description + "Support for Advanced Encryption Standard (AES) Key Wrap."; + } + + feature replay-protection-only { + description + "Provide replay protection without any authentication + as required by protocols such as Bidirectional + Forwarding Detection (BFD)."; + } + identity crypto-algorithm { + description + "Base identity of cryptographic algorithm options."; + } + + identity hmac-sha-1-12 { + base crypto-algorithm; + if-feature "crypto-hmac-sha-1-12"; + description + "The HMAC-SHA1-12 algorithm."; + } + + identity aes-cmac-prf-128 { + base crypto-algorithm; + if-feature "aes-cmac-prf-128"; + description + "The AES-CMAC-PRF-128 algorithm - required by + RFC 5926 for TCP-AO key derivation functions."; + } + + identity md5 { + base crypto-algorithm; + description + "The MD5 algorithm."; + } + + identity sha-1 { + base crypto-algorithm; + description + "The SHA-1 algorithm."; + } + + identity hmac-sha-1 { + base crypto-algorithm; + description + "HMAC-SHA-1 authentication algorithm."; + } + + identity hmac-sha-256 { + base crypto-algorithm; + description + "HMAC-SHA-256 authentication algorithm."; + } + + identity hmac-sha-384 { + base crypto-algorithm; + description + "HMAC-SHA-384 authentication algorithm."; + } + + identity hmac-sha-512 { + base crypto-algorithm; + description + "HMAC-SHA-512 authentication algorithm."; + } + + identity cleartext { + base crypto-algorithm; + if-feature "cleartext"; + description + "cleartext."; + } + + identity replay-protection-only { + base crypto-algorithm; + if-feature "replay-protection-only"; + description + "Provide replay protection without any authentication as + required by protocols such as Bidirectional Forwarding + Detection (BFD)."; + } + + typedef key-chain-ref { + type leafref { + path + "/key-chain:key-chains/key-chain:key-chain/key-chain:name"; + } + description + "This type is used by data models that need to reference + configured key chains."; + } + + grouping lifetime { + description + "Key lifetime specification."; + choice lifetime { + default "always"; + description + "Options for specifying key accept or send lifetimes"; + case always { + leaf always { + type empty; + description + "Indicates key lifetime is always valid."; + } + } + case start-end-time { + leaf start-date-time { + type yang:date-and-time; + description + "Start time."; + } + choice end-time { + default "infinite"; + description + "End-time setting."; + case infinite { + leaf no-end-time { + type empty; + description + "Indicates key lifetime end-time is infinite."; + } + } + case duration { + leaf duration { + type uint32 { + range "1..2147483646"; + } + units "seconds"; + description + "Key lifetime duration, in seconds"; + } + } + case end-date-time { + leaf end-date-time { + type yang:date-and-time; + description + "End time."; + } + } + } + } + } + } + + container key-chains { + description + "All configured key-chains on the device."; + list key-chain { + key "name"; + description + "List of key-chains."; + leaf name { + type string; + description + "Name of the key-chain."; + } + leaf description { + type string; + description + "A description of the key-chain"; + } + container accept-tolerance { + if-feature "accept-tolerance"; + description + "Tolerance for key lifetime acceptance (seconds)."; + leaf duration { + type uint32; + units "seconds"; + default "0"; + description + "Tolerance range, in seconds."; + } + } + leaf last-modified-timestamp { + type yang:date-and-time; + config false; + description + "Timestamp of the most recent update to the key-chain"; + } + list key { + key "key-id"; + description + "Single key in key chain."; + leaf key-id { + type uint64; + description + "Numeric value uniquely identifying the key"; + } + container lifetime { + description + "Specify a key's lifetime."; + choice lifetime { + description + "Options for specification of send and accept + lifetimes."; + case send-and-accept-lifetime { + description + "Send and accept key have the same lifetime."; + container send-accept-lifetime { + description + "Single lifetime specification for both + send and accept lifetimes."; + uses lifetime; + } + } + case independent-send-accept-lifetime { + if-feature "independent-send-accept-lifetime"; + description + "Independent send and accept key lifetimes."; + container send-lifetime { + description + "Separate lifetime specification for send + lifetime."; + uses lifetime; + } + container accept-lifetime { + description + "Separate lifetime specification for accept + lifetime."; + uses lifetime; + } + } + } + } + leaf crypto-algorithm { + type identityref { + base crypto-algorithm; + } + mandatory true; + description + "Cryptographic algorithm associated with key."; + } + container key-string { + description + "The key string."; + nacm:default-deny-all; + choice key-string-style { + description + "Key string styles"; + case keystring { + leaf keystring { + type string; + description + "Key string in ASCII format."; + } + } + case hexadecimal { + if-feature "hex-key-string"; + leaf hexadecimal-string { + type yang:hex-string; + description + "Key in hexadecimal string format. When compared + to ASCII, specification in hexadecimal affords + greater key entropy with the same number of + internal key-string octets. Additionally, it + discourages usage of well-known words or + numbers."; + } + } + } + } + leaf send-lifetime-active { + type boolean; + config false; + description + "Indicates if the send lifetime of the + key-chain key is currently active."; + } + leaf accept-lifetime-active { + type boolean; + config false; + description + "Indicates if the accept lifetime of the + key-chain key is currently active."; + } + } + } + container aes-key-wrap { + if-feature "aes-key-wrap"; + description + "AES Key Wrap encryption for key-chain key-strings. The + encrypted key-strings are encoded as hexadecimal key + strings using the hex-key-string leaf."; + leaf enable { + type boolean; + default "false"; + description + "Enable AES Key Wrap encryption."; + } + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-netconf-acm@2018-02-14.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-netconf-acm@2018-02-14.yang new file mode 100644 index 0000000000000000000000000000000000000000..bf4855faf0508a152471f6c6c8f756581b8ebb96 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-netconf-acm@2018-02-14.yang @@ -0,0 +1,464 @@ +module ietf-netconf-acm { + + namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm"; + + prefix nacm; + + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: <https://datatracker.ietf.org/wg/netconf/> + WG List: <mailto:netconf@ietf.org> + + Author: Andy Bierman + <mailto:andy@yumaworks.com> + + Author: Martin Bjorklund + <mailto:mbj@tail-f.com>"; + + description + "Network Configuration Access Control Model. + + Copyright (c) 2012 - 2018 IETF Trust and the persons + identified as authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD + License set forth in Section 4.c of the IETF Trust's + Legal Provisions Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8341; see + the RFC itself for full legal notices."; + + revision "2018-02-14" { + description + "Added support for YANG 1.1 actions and notifications tied to + data nodes. Clarified how NACM extensions can be used by + other data models."; + reference + "RFC 8341: Network Configuration Access Control Model"; + } + + revision "2012-02-22" { + description + "Initial version."; + reference + "RFC 6536: Network Configuration Protocol (NETCONF) + Access Control Model"; + } + + /* + * Extension statements + */ + + extension default-deny-write { + description + "Used to indicate that the data model node + represents a sensitive security system parameter. + + If present, the NETCONF server will only allow the designated + 'recovery session' to have write access to the node. An + explicit access control rule is required for all other users. + + If the NACM module is used, then it must be enabled (i.e., + /nacm/enable-nacm object equals 'true'), or this extension + is ignored. + + The 'default-deny-write' extension MAY appear within a data + definition statement. It is ignored otherwise."; + } + + extension default-deny-all { + description + "Used to indicate that the data model node + controls a very sensitive security system parameter. + + If present, the NETCONF server will only allow the designated + 'recovery session' to have read, write, or execute access to + the node. An explicit access control rule is required for all + other users. + + If the NACM module is used, then it must be enabled (i.e., + /nacm/enable-nacm object equals 'true'), or this extension + is ignored. + + The 'default-deny-all' extension MAY appear within a data + definition statement, 'rpc' statement, or 'notification' + statement. It is ignored otherwise."; + } + + /* + * Derived types + */ + + typedef user-name-type { + type string { + length "1..max"; + } + description + "General-purpose username string."; + } + + typedef matchall-string-type { + type string { + pattern '\*'; + } + description + "The string containing a single asterisk '*' is used + to conceptually represent all possible values + for the particular leaf using this data type."; + } + + typedef access-operations-type { + type bits { + bit create { + description + "Any protocol operation that creates a + new data node."; + } + bit read { + description + "Any protocol operation or notification that + returns the value of a data node."; + } + bit update { + description + "Any protocol operation that alters an existing + data node."; + } + bit delete { + description + "Any protocol operation that removes a data node."; + } + bit exec { + description + "Execution access to the specified protocol operation."; + } + } + description + "Access operation."; + } + + typedef group-name-type { + type string { + length "1..max"; + pattern '[^\*].*'; + } + description + "Name of administrative group to which + users can be assigned."; + } + + typedef action-type { + type enumeration { + enum permit { + description + "Requested action is permitted."; + } + enum deny { + description + "Requested action is denied."; + } + } + description + "Action taken by the server when a particular + rule matches."; + } + + typedef node-instance-identifier { + type yang:xpath1.0; + description + "Path expression used to represent a special + data node, action, or notification instance-identifier + string. + + A node-instance-identifier value is an + unrestricted YANG instance-identifier expression. + All the same rules as an instance-identifier apply, + except that predicates for keys are optional. If a key + predicate is missing, then the node-instance-identifier + represents all possible server instances for that key. + + This XML Path Language (XPath) expression is evaluated in the + following context: + + o The set of namespace declarations are those in scope on + the leaf element where this type is used. + + o The set of variable bindings contains one variable, + 'USER', which contains the name of the user of the + current session. + + o The function library is the core function library, but + note that due to the syntax restrictions of an + instance-identifier, no functions are allowed. + + o The context node is the root node in the data tree. + + The accessible tree includes actions and notifications tied + to data nodes."; + } + + /* + * Data definition statements + */ + + container nacm { + nacm:default-deny-all; + + description + "Parameters for NETCONF access control model."; + + leaf enable-nacm { + type boolean; + default "true"; + description + "Enables or disables all NETCONF access control + enforcement. If 'true', then enforcement + is enabled. If 'false', then enforcement + is disabled."; + } + + leaf read-default { + type action-type; + default "permit"; + description + "Controls whether read access is granted if + no appropriate rule is found for a + particular read request."; + } + + leaf write-default { + type action-type; + default "deny"; + description + "Controls whether create, update, or delete access + is granted if no appropriate rule is found for a + particular write request."; + } + + leaf exec-default { + type action-type; + default "permit"; + description + "Controls whether exec access is granted if no appropriate + rule is found for a particular protocol operation request."; + } + + leaf enable-external-groups { + type boolean; + default "true"; + description + "Controls whether the server uses the groups reported by the + NETCONF transport layer when it assigns the user to a set of + NACM groups. If this leaf has the value 'false', any group + names reported by the transport layer are ignored by the + server."; + } + + leaf denied-operations { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that a + protocol operation request was denied."; + } + + leaf denied-data-writes { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that a + protocol operation request to alter + a configuration datastore was denied."; + } + + leaf denied-notifications { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that + a notification was dropped for a subscription because + access to the event type was denied."; + } + + container groups { + description + "NETCONF access control groups."; + + list group { + key name; + + description + "One NACM group entry. This list will only contain + configured entries, not any entries learned from + any transport protocols."; + + leaf name { + type group-name-type; + description + "Group name associated with this entry."; + } + + leaf-list user-name { + type user-name-type; + description + "Each entry identifies the username of + a member of the group associated with + this entry."; + } + } + } + + list rule-list { + key name; + ordered-by user; + description + "An ordered collection of access control rules."; + + leaf name { + type string { + length "1..max"; + } + description + "Arbitrary name assigned to the rule-list."; + } + leaf-list group { + type union { + type matchall-string-type; + type group-name-type; + } + description + "List of administrative groups that will be + assigned the associated access rights + defined by the 'rule' list. + + The string '*' indicates that all groups apply to the + entry."; + } + + list rule { + key name; + ordered-by user; + description + "One access control rule. + + Rules are processed in user-defined order until a match is + found. A rule matches if 'module-name', 'rule-type', and + 'access-operations' match the request. If a rule + matches, the 'action' leaf determines whether or not + access is granted."; + + leaf name { + type string { + length "1..max"; + } + description + "Arbitrary name assigned to the rule."; + } + + leaf module-name { + type union { + type matchall-string-type; + type string; + } + default "*"; + description + "Name of the module associated with this rule. + + This leaf matches if it has the value '*' or if the + object being accessed is defined in the module with the + specified module name."; + } + choice rule-type { + description + "This choice matches if all leafs present in the rule + match the request. If no leafs are present, the + choice matches all requests."; + case protocol-operation { + leaf rpc-name { + type union { + type matchall-string-type; + type string; + } + description + "This leaf matches if it has the value '*' or if + its value equals the requested protocol operation + name."; + } + } + case notification { + leaf notification-name { + type union { + type matchall-string-type; + type string; + } + description + "This leaf matches if it has the value '*' or if its + value equals the requested notification name."; + } + } + + case data-node { + leaf path { + type node-instance-identifier; + mandatory true; + description + "Data node instance-identifier associated with the + data node, action, or notification controlled by + this rule. + + Configuration data or state data + instance-identifiers start with a top-level + data node. A complete instance-identifier is + required for this type of path value. + + The special value '/' refers to all possible + datastore contents."; + } + } + } + + leaf access-operations { + type union { + type matchall-string-type; + type access-operations-type; + } + default "*"; + description + "Access operations associated with this rule. + + This leaf matches if it has the value '*' or if the + bit corresponding to the requested operation is set."; + } + + leaf action { + type action-type; + mandatory true; + description + "The access control action associated with the + rule. If a rule has been determined to match a + particular request, then this object is used + to determine whether to permit or deny the + request."; + } + + leaf comment { + type string; + description + "A textual description of the access rule."; + } + } + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/ietf-network-slice-service.txt b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-slice-service.txt similarity index 100% rename from src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/ietf-network-slice-service.txt rename to src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-slice-service.txt diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-slice-service@2024-08-28.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-slice-service@2024-08-28.yang new file mode 100644 index 0000000000000000000000000000000000000000..d72dd1ed38c6b098c70ab824f98e8029aef7d137 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-slice-service@2024-08-28.yang @@ -0,0 +1,1375 @@ +module ietf-network-slice-service { + yang-version 1.1; + namespace + "urn:ietf:params:xml:ns:yang:ietf-network-slice-service"; + prefix ietf-nss; + + import ietf-inet-types { + prefix inet; + reference + "RFC 6991: Common YANG Types"; + } + import ietf-yang-types { + prefix yang; + reference + "RFC 6991: Common YANG Data Types"; + } + import ietf-geo-location { + prefix geo; + reference + "RFC 9179: A YANG Grouping for Geographic Locations"; + } + import ietf-vpn-common { + prefix vpn-common; + reference + "RFC 9181: A Common YANG Data Model for Layer 2 and Layer 3 + VPNs"; + } + import ietf-network { + prefix nw; + reference + "RFC 8345: A YANG Data Model for Network Topologies"; + } + import ietf-network-topology { + prefix nt; + reference + "RFC 8345: A YANG Data Model for Network + Topologies, Section 6.2"; + } + import ietf-ac-common { + prefix ac-common; + reference + "RFC BBBB: A Common YANG Data Model for Attachment Circuits"; + } + import ietf-ac-svc { + prefix ac-svc; + reference + "RFC CCCC: YANG Data Models for Bearers and 'Attachment + Circuits'-as-a-Service (ACaaS)"; + } + import ietf-te-types { + prefix te-types; + reference + "RFC DDDD: Common YANG Types for Traffic Engineering"; + } + import ietf-te-packet-types { + prefix te-packet-types; + reference + "RFC DDDD: Common YANG Data Types for Traffic Engineering"; + } + + organization + "IETF Traffic Engineering Architecture and Signaling (TEAS) + Working Group"; + contact + "WG Web: <https://datatracker.ietf.org/wg/teas/> + WG List: <mailto:teas@ietf.org> + + Editor: Bo Wu + <lana.wubo@huawei.com> + Editor: Dhruv Dhody + <dhruv.ietf@gmail.com> + Editor: Reza Rokui + <rrokui@ciena.com> + Editor: Tarek Saad + <tsaad@cisco.com> + Editor: John Mullooly + <jmullool@cisco.com>"; + description + "This YANG module defines a service model for the RFC 9543 + Network Slice Service. + + Copyright (c) 2024 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject to + the license terms contained in, the Revised BSD License set + forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC AAAA; see the + RFC itself for full legal notices."; + + revision 2024-08-28 { + description + "Initial revision."; + reference + "RFC AAAA: A YANG Data Model for the RFC 9543 Network Slice + Service"; + } + + /* Identities */ + + identity service-tag-type { + description + "Base identity of Network Slice Service tag type, which is + used for management purposes, such as classification + (e.g., customer names) and policy constraints + (e.g., Layer 2 or Layer 3 technology realization)."; + } + + identity customer { + base service-tag-type; + description + "The Network Slice Service customer name tag type, + e.g., adding tags with 'customer name' when multiple actual + customers use the same Network Slice Service."; + } + + identity service { + base service-tag-type; + description + "The Network Slice Service tag type, which can indicate the + technical constraints used during service realization, + for example, Layer 2 or Layer 3 technologies."; + } + + identity opaque { + base service-tag-type; + description + "An opaque type, which can be used for future use, + such as filtering of services."; + } + + identity attachment-circuit-tag-type { + description + "Base identity for the attachment circuit tag type."; + } + + identity vlan-id { + base attachment-circuit-tag-type; + description + "Identity for VLAN ID tag type, 802.1Q dot1Q."; + reference + "IEEE Std 802.1Q: IEEE Standard for Local and Metropolitan + Area Networks--Bridges and Bridged + Networks"; + } + + identity cvlan-id { + base attachment-circuit-tag-type; + description + "Identity for C-VLAN ID tag type, 802.1ad QinQ VLAN IDs."; + reference + "IEEE Std 802.1ad: IEEE Standard for Local and Metropolitan + Area Networks---Virtual Bridged Local + Area Networks---Amendment 4: Provider + Bridges"; + } + + identity svlan-id { + base attachment-circuit-tag-type; + description + "Identity for S-VLAN ID tag type, 802.1ad QinQ VLAN IDs."; + reference + "IEEE Std 802.1ad: IEEE Standard for Local and Metropolitan + Area Networks---Virtual Bridged Local + Area Networks---Amendment 4: Provider + Bridges"; + } + + identity ip-address-mask { + base attachment-circuit-tag-type; + description + "Identity for IP address mask tag type."; + } + + identity service-isolation-type { + description + "Base identity for Network Slice Service isolation type."; + } + + identity traffic-isolation { + base service-isolation-type; + description + "Specify the requirement for separating the traffic of the + customer's Network Slice Service from other services, + which may be provided by the service provider using VPN + technologies, such as L3VPN, L2VPN, EVPN, etc."; + } + + identity service-security-type { + description + "Base identity for Network Slice Service security type."; + } + + identity authentication { + base service-security-type; + description + "Indicates that the Slice Service requires authentication."; + } + + identity integrity { + base service-security-type; + description + "Indicates that the Slice Service requires data integrity."; + } + + identity encryption { + base service-security-type; + description + "Indicates that the Slice Service requires data encryption."; + } + + identity point-to-point { + base vpn-common:vpn-topology; + description + "Identity for point-to-point Network Slice + Service connectivity."; + } + + identity point-to-multipoint { + base vpn-common:vpn-topology; + description + "Identity for point-to-multipoint Network Slice + Service connectivity."; + } + + identity multipoint-to-multipoint { + base vpn-common:vpn-topology; + description + "Identity for multipoint-to-multipoint Network Slice + Service connectivity."; + } + + identity multipoint-to-point { + base vpn-common:vpn-topology; + description + "Identity for multipoint-to-point Network Slice + Service connectivity."; + } + + identity sender-role { + base vpn-common:role; + description + "Indicates that an SDP is acting as a sender."; + } + + identity receiver-role { + base vpn-common:role; + description + "Indicates that an SDP is acting as a receiver."; + } + + identity service-slo-metric-type { + description + "Base identity for Network Slice Service SLO metric type."; + } + + identity one-way-bandwidth { + base service-slo-metric-type; + description + "SLO bandwidth metric. Minimum guaranteed bandwidth between + two SDPs at any time and is measured unidirectionally."; + } + + identity two-way-bandwidth { + base service-slo-metric-type; + description + "SLO bandwidth metric. Minimum guaranteed bandwidth between + two SDPs at any time."; + } + + identity shared-bandwidth { + base service-slo-metric-type; + description + "The shared SLO bandwidth bound. It is the limit on the + bandwidth that can be shared amongst a group of + connectivity constructs of a Slice Service."; + } + + identity one-way-delay-maximum { + base service-slo-metric-type; + description + "The SLO objective of this metric is the upper bound of network + delay when transmitting between two SDPs."; + reference + "RFC 7679: A One-Way Delay Metric for IP Performance + Metrics (IPPM)"; + } + + identity one-way-delay-percentile { + base service-slo-metric-type; + description + "The SLO objective of this metric is percentile objective of + network delay when transmitting between two SDPs. + The metric is defined in RFC7679."; + reference + "RFC 7679: A One-Way Delay Metric for IP Performance + Metrics (IPPM)"; + } + + identity two-way-delay-maximum { + base service-slo-metric-type; + description + "SLO two-way delay is the upper bound of network delay when + transmitting between two SDPs"; + reference + "RFC 2681: A Round-trip Delay Metric for IPPM"; + } + + identity two-way-delay-percentile { + base service-slo-metric-type; + description + "The SLO objective of this metric is the percentile + objective of network delay when the traffic transmitting + between two SDPs."; + reference + "RFC 2681: A Round-trip Delay Metric for IPPM"; + } + + identity one-way-delay-variation-maximum { + base service-slo-metric-type; + description + "The SLO objective of this metric is maximum bound of the + difference in the one-way delay between sequential packets + between two SDPs."; + reference + "RFC 3393: IP Packet Delay Variation Metric for IP Performance + Metrics (IPPM)"; + } + + identity one-way-delay-variation-percentile { + base service-slo-metric-type; + description + "The SLO objective of this metric is the percentile objective + in the one-way delay between sequential packets between two + SDPs."; + reference + "RFC 3393: IP Packet Delay Variation Metric for IP Performance + Metrics (IPPM)"; + } + + identity two-way-delay-variation-maximum { + base service-slo-metric-type; + description + "SLO two-way delay variation is the difference in the + round-trip delay between sequential packets between two + SDPs."; + reference + "RFC 5481: Packet Delay Variation Applicability Statement"; + } + + identity two-way-delay-variation-percentile { + base service-slo-metric-type; + description + "The SLO objective of this metric is the percentile objective + in the round-trip delay between sequential packets between + two SDPs."; + reference + "RFC 5481: Packet Delay Variation Applicability Statement"; + } + + identity one-way-packet-loss { + base service-slo-metric-type; + description + "This metric type refers to the ratio of packets dropped + to packets transmitted between two SDPs in one-way."; + reference + "RFC 7680: A One-Way Loss Metric for IP Performance + Metrics (IPPM)"; + } + + identity two-way-packet-loss { + base service-slo-metric-type; + description + "This metric type refers to the ratio of packets dropped + to packets transmitted between two SDPs in two-way."; + reference + "RFC 7680: A One-Way Loss Metric for IP Performance + Metrics (IPPM)"; + } + + identity availability-type { + description + "Base identity for availability."; + } + + identity six-nines { + base availability-type; + description + "Specifies the availability level: 99.9999%"; + } + + identity five-nines { + base availability-type; + description + "Specifies the availability level: 99.999%"; + } + + identity four-nines { + base availability-type; + description + "Specifies the availability level: 99.99%"; + } + + identity three-nines { + base availability-type; + description + "Specifies the availability level: 99.9%"; + } + + identity two-nines { + base availability-type; + description + "Specifies the availability level: 99%"; + } + + identity service-match-type { + description + "Base identity for Network Slice Service traffic + match type."; + } + identity phy-interface { + base service-match-type; + description + "Uses the physical interface as match criteria for + Slice Service traffic."; + } + + identity vlan { + base service-match-type; + description + "Uses the VLAN ID as match criteria for the Slice Service + traffic."; + } + + identity label { + base service-match-type; + description + "Uses the MPLS label as match criteria for the Slice Service + traffic."; + } + + identity source-ip-prefix { + base service-match-type; + description + "Uses source IP prefix as match criteria for the Slice Service + traffic. Examples of 'value' of this match type are + '192.0.2.0/24' and '2001:db8::1/64'."; + } + + identity destination-ip-prefix { + base service-match-type; + description + "Uses destination IP prefix as match criteria for the Slice + Service traffic. Examples of 'value' of this match type are + '203.0.113.1/32' and '2001:db8::2/128'."; + } + + identity dscp { + base service-match-type; + description + "Uses DSCP field in the IP packet header as match criteria + for the Slice Service traffic."; + } + + identity acl { + base service-match-type; + description + "Uses Access Control List (ACL) as match criteria + for the Slice Service traffic."; + reference + "RFC 8519: YANG Data Model for Network Access Control + Lists (ACLs)"; + } + + identity any { + base service-match-type; + description + "Matches any Slice Service traffic."; + } + + identity source-tcp-port { + base service-match-type; + description + "Uses source TCP port as match criteria for the Slice Service + traffic. Examples of 'value' of this match type are + '8080' and '22'."; + } + + identity destination-tcp-port { + base service-match-type; + description + "Uses destination TCP port as match criteria for the Slice + Service traffic. Examples of 'value' of this match type are + '8080' and '22'."; + } + + identity source-udp-port { + base service-match-type; + description + "Uses source UDP port as match criteria for the Slice Service + traffic. Examples of 'value' of this match type are + '53', '67' and '68'."; + } + +identity destination-udp-port { + base service-match-type; + description + "Uses destination UDP port as match criteria for the Slice + Service traffic. Examples of 'value' of this match type are + '53', '67' and '68'."; +} + + identity slo-sle-policy-override { + description + "Base identity for SLO/SLE policy override options."; + } + + identity full-override { + base slo-sle-policy-override; + description + "The SLO/SLE policy defined at the child level overrides a + parent SLO/SLE policy, which means that no SLO/SLEs are + inherited from parent if a child SLO/SLE policy exists."; + } + + identity partial-override { + base slo-sle-policy-override; + description + "The SLO/SLE policy defined at the child level updates the + parent SLO/SLE policy. For example, if a specific SLO is + defined at the child level, that specific SLO overrides + the one inherited from a parent SLO/SLE policy, while all + other SLOs in the parent SLO-SLE policy still apply."; + } + + /* Typedef */ + + typedef percentage { + type decimal64 { + fraction-digits 5; + range "0..100"; + } + description + "Percentage to 5 decimal places."; + } + + typedef percentile { + type decimal64 { + fraction-digits 3; + range "0..100"; + } + description + "The percentile is a value between 0 and 100 + to 3 decimal places, e.g., 10.000, 99.900,99.990, etc. + For example, for a given one-way delay measurement, + if the percentile is set to 95.000 and the 95th percentile + one-way delay is 2 milliseconds, then the 95 percent of + the sample value is less than or equal to 2 milliseconds."; + } + + typedef slice-template-ref { + type leafref { + path "/ietf-nss:network-slice-services" + + "/ietf-nss:slo-sle-templates" + + "/ietf-nss:slo-sle-template" + + "/ietf-nss:id"; + } + description + "This type is used by data models that need to reference + Network Slice templates."; + } + + typedef slice-service-ref { + type leafref { + path + "/ietf-nss:network-slice-services/ietf-nss:slice-service" + + "/ietf-nss:id"; + } + description + "Defines a reference to a slice service that can be used + by other modules."; + } + + /* Groupings */ + + grouping service-slos { + description + "A reusable grouping for directly measurable objectives of + a Slice Service."; + container slo-policy { + description + "Contains the SLO policy."; + list metric-bound { + key "metric-type"; + description + "List of Slice Service metric bounds."; + leaf metric-type { + type identityref { + base service-slo-metric-type; + } + description + "Identifies SLO metric type of the Slice Service."; + } + leaf metric-unit { + type string; + mandatory true; + description + "The metric unit of the parameter. For example, + for time units, where the options are hours, minutes, + seconds, milliseconds, microseconds, and nanoseconds; + for bandwidth units, where the options are bps, Kbps, + Mbps, Gbps; for the packet loss rate unit, + the options can be a percentage."; + } + leaf value-description { + type string; + description + "The description of the provided value."; + } + leaf percentile-value { + type percentile; + description + "The percentile value of the metric type."; + } + leaf bound { + type uint64; + description + "The bound on the Slice Service connection metric. + When set to zero, this indicates an unbounded + upper limit for the specific metric-type."; + } + } + leaf availability { + type identityref { + base availability-type; + } + description + "Service availability level"; + } + leaf mtu { + type uint32; + units "bytes"; + description + "Specifies the maximum length of Layer 2 data + packets of the Slice Service. + If the customer sends packets that are longer than the + requested service MTU, the network may discard them + (or for IPv4, fragment them). + This service MTU takes precedence over the MTUs of + all attachment circuits (ACs). The value needs to be + less than or equal to the minimum MTU value of + all ACs in the SDPs."; + } + } + } + + grouping service-sles { + description + "A reusable grouping for indirectly measurable objectives of + a Slice Service."; + container sle-policy { + description + "Contains the SLE policy."; + leaf-list security { + type identityref { + base service-security-type; + } + description + "The security functions that the customer requests + the operator to apply to traffic between the two SDPs."; + } + leaf-list isolation { + type identityref { + base service-isolation-type; + } + description + "The Slice Service isolation requirement."; + } + leaf max-occupancy-level { + type uint8 { + range "1..100"; + } + description + "The maximal occupancy level specifies the number of flows + to be admitted and optionally a maximum number of + countable resource units (e.g., IP or MAC addresses) + a Network Slice Service can consume."; + } + container path-constraints { + description + "Container for the policy of path constraints + applicable to the Slice Service."; + container service-functions { + description + "Container for the policy of service function + applicable to the Slice Service."; + } + container diversity { + description + "Container for the policy of disjointness + applicable to the Slice Service."; + leaf diversity-type { + type te-types:te-path-disjointness; + description + "The type of disjointness on Slice Service, i.e., + across all connectivity constructs."; + } + } + } + } + } + + grouping slice-service-template { + description + "A reusable grouping for Slice Service templates."; + container slo-sle-templates { + description + "Contains a set of Slice Service templates."; + list slo-sle-template { + key "id"; + description + "List for SLO and SLE template identifiers."; + leaf id { + type string; + description + "Identification of the Service Level Objective (SLO) + and Service Level Expectation (SLE) template to be used. + Local administration meaning."; + } + leaf description { + type string; + description + "Describes the SLO and SLE policy template."; + } + leaf template-ref { + type slice-template-ref; + description + "The reference to a standard template. When set it + indicates the base template over which further + SLO/SLE policy changes are made."; + } + uses service-slos; + uses service-sles; + } + } + } + + grouping service-slo-sle-policy { + description + "Slice service policy grouping."; + choice slo-sle-policy { + description + "Choice for SLO and SLE policy template. + Can be standard template or customized template."; + case standard { + description + "Standard SLO template."; + leaf slo-sle-template { + type slice-template-ref; + description + "Standard SLO and SLE template to be used."; + } + } + case custom { + description + "Customized SLO and SLE template."; + container service-slo-sle-policy { + description + "Contains the SLO and SLE policy."; + leaf description { + type string; + description + "Describes the SLO and SLE policy."; + } + uses service-slos; + uses service-sles; + } + } + } + } + + grouping service-qos { + description + "Grouping for the Slice Service QoS policy."; + container incoming-qos-policy { + description + "The QoS policy imposed on ingress direction of the traffic, + from the customer network or from another provider's + network."; + leaf qos-policy-name { + type string; + description + "The name of the QoS policy that is applied to the + attachment circuit. The name can reference a QoS + profile that is pre-provisioned on the device."; + } + container rate-limits { + description + "Container for the asymmetric traffic control."; + uses ac-common:bandwidth-parameters; + container classes { + description + "Container for service class bandwidth control."; + list cos { + key "cos-id"; + description + "List of Class of Services."; + leaf cos-id { + type uint8; + description + "Identifier of the CoS, indicated by + a Differentiated Services Code Point + (DSCP) or a CE-CLAN CoS (802.1p) + value in the service frame."; + reference + "IEEE Std 802.1Q: Bridges and Bridged + Networks"; + } + uses ac-common:bandwidth-parameters; + } + } + } + } + container outgoing-qos-policy { + description + "The QoS policy imposed on egress direction of the traffic, + towards the customer network or towards another + provider's network."; + leaf qos-policy-name { + type string; + description + "The name of the QoS policy that is applied to the + attachment circuit. The name can reference a QoS + profile that is pre-provisioned on the device."; + } + container rate-limits { + description + "The rate-limit imposed on outgoing traffic."; + uses ac-common:bandwidth-parameters; + container classes { + description + "Container for classes."; + list cos { + key "cos-id"; + description + "List of Class of Services."; + leaf cos-id { + type uint8; + description + "Identifier of the CoS, indicated by + a Differentiated Services Code Point + (DSCP) or a CE-CLAN CoS (802.1p) + value in the service frame."; + reference + "IEEE Std 802.1Q: Bridges and Bridged + Networks"; + } + uses ac-common:bandwidth-parameters; + } + } + } + } + } + + grouping service-slo-sle-policy-override { + description + "Slice Service policy override grouping."; + leaf service-slo-sle-policy-override { + type identityref { + base slo-sle-policy-override; + } + description + "SLO/SLE policy override option."; + } + } + + grouping connectivity-construct-monitoring-metrics { + description + "Grouping for connectivity construct monitoring metrics."; + uses + te-packet-types:one-way-performance-metrics-gauge-packet; + uses + te-packet-types:two-way-performance-metrics-gauge-packet; + } + /* Main Network Slice Services Container */ + + container network-slice-services { + description + "Contains a list of Network Slice Services"; + uses slice-service-template; + list slice-service { + key "id"; + description + "A Slice Service is identified by a service id."; + leaf id { + type string; + description + "A unique Slice Service identifier within an NSC."; + } + leaf description { + type string; + description + "Textual description of the Slice Service."; + } + container service-tags { + description + "Container for a list of service tags for management + purposes, such as policy constraints + (e.g., Layer 2 or Layer 3 technology realization), + classification (e.g., customer names, opaque values)."; + list tag-type { + key "tag-type"; + description + "The service tag list."; + leaf tag-type { + type identityref { + base service-tag-type; + } + description + "Slice Service tag type, e.g., realization technology + constraints, customer name, or other customer-defined + opaque types."; + } + leaf-list value { + type string; + description + "The tag values, e.g., 5G customer names when multiple + customers share the same Slice Service in 5G scenario, + or Slice realization technology (such as Layer 2 or + Layer 3)."; + } + } + } + uses service-slo-sle-policy; + leaf compute-only { + type empty; + description + "When present, this is a feasibility check. That is, no + resources are reserved in the network."; + } + uses ac-common:service-status; + container sdps { + description + "Slice Service SDPs."; + list sdp { + key "id"; + min-elements 2; + description + "List of SDPs in this Slice Service."; + leaf id { + type string; + description + "The unique identifier of the SDP within the scope of + an NSC."; + } + leaf description { + type string; + description + "Provides a description of the SDP."; + } + uses geo:geo-location; + leaf node-id { + type string; + description + "A unique identifier of an edge node of the SDP + within the scope of the NSC."; + } + leaf-list sdp-ip-address { + type inet:ip-address; + description + "IPv4 or IPv6 address of the SDP."; + } + leaf tp-ref { + type leafref { + path + "/nw:networks/nw:network[nw:network-id=" + + "current()/../../../custom-topology/network-ref]/" + + "nw:node/nt:termination-point/nt:tp-id"; + } + description + "A reference to Termination Point (TP) in the custom + topology"; + reference + "RFC 8345: A YANG Data Model for Network Topologies"; + } + container service-match-criteria { + description + "Describes the Slice Service match criteria."; + list match-criterion { + key "index"; + description + "List of the Slice Service traffic match criteria."; + leaf index { + type uint32; + description + "The identifier of a match criteria."; + } + list match-type { + key "type"; + description + "List of the Slice Service traffic match types."; + leaf type { + type identityref { + base service-match-type; + } + mandatory true; + description + "Indicates the match type of the entry in the + list of the Slice Service match criteria."; + } + leaf-list value { + type string; + description + "Provides a value for the Slice Service match + criteria, e.g., IP prefix, VLAN ID, or + ACL name."; + } + } + leaf target-connection-group-id { + type leafref { + path + "../../../../../ietf-nss:connection-groups" + + "/ietf-nss:connection-group" + + "/ietf-nss:id"; + } + mandatory true; + description + "Reference to the Slice Service connection group."; + } + leaf connection-group-sdp-role { + type identityref { + base vpn-common:role; + } + description + "Specifies the role of SDP in the connection group + When the service connection type is MP2MP, + such as hub and spoke service connection type. + In addition, this helps to create connectivity + construct automatically, rather than explicitly + specifying each one."; + } + leaf target-connectivity-construct-id { + type leafref { + path + "../../../../../ietf-nss:connection-groups" + + "/ietf-nss:connection-group[ietf-nss:id=" + + "current()/../target-connection-group-id]" + + "/ietf-nss:connectivity-construct/ietf-nss:id"; + } + description + "Reference to a Network Slice connection + construct."; + } + } + } + uses service-qos; + container sdp-peering { + description + "Describes SDP peering attributes."; + leaf-list peer-sap-id { + type string; + description + "Indicates the reference to the remote endpoints of + the attachment circuits. This information can be + used for correlation purposes, such as identifying + SAPs of provider equipments when requesting + a service with CE based SDP attributes."; + reference + "RFC 9408: A YANG Network Data Model for Service + Attachment Points (SAPs)"; + } + container protocols { + description + "Serves as an augmentation target. + Protocols can be augmented into this container, + e.g., BGP, static routing."; + } + } + leaf-list ac-svc-ref { + type ac-svc:attachment-circuit-reference; + description + "A reference to the ACs that have been created before + the slice creation."; + reference + "RFC CCCC: YANG Data Models for Bearers and + 'Attachment Circuits'-as-a-Service (ACaaS)"; + } + leaf ce-mode { + type boolean; + description + "Indicates that SDP is on the CE."; + } + container attachment-circuits { + description + "List of attachment circuits."; + list attachment-circuit { + key "id"; + description + "The Network Slice Service SDP attachment circuit + related parameters."; + leaf id { + type string; + description + "The identifier of attachment circuit."; + } + leaf description { + type string; + description + "The attachment circuit's description."; + } + leaf ac-svc-ref { + type ac-svc:attachment-circuit-reference; + description + "A reference to the AC service that has been + created before the slice creation."; + reference + "RFC CCCC: YANG Data Models for Bearers and + 'Attachment Circuits'-as-a-Service (ACaaS)"; + } + leaf ac-node-id { + type string; + description + "The attachment circuit node ID in the case of + multi-homing."; + } + leaf ac-tp-id { + type string; + description + "The termination port ID of the + attachment circuit."; + } + leaf ac-ipv4-address { + type inet:ipv4-address; + description + "The IPv4 address of the AC."; + } + leaf ac-ipv4-prefix-length { + type uint8; + description + "The IPv4 subnet prefix length expressed in bits."; + } + leaf ac-ipv6-address { + type inet:ipv6-address; + description + "The IPv6 address of the AC."; + } + leaf ac-ipv6-prefix-length { + type uint8; + description + "The IPv6 subnet prefix length expressed in bits."; + } + leaf mtu { + type uint32; + units "bytes"; + description + "Maximum size of the Slice Service Layer 2 data + packet that can traverse an SDP."; + } + container ac-tags { + description + "Container for the attachment circuit tags."; + list ac-tag { + key "tag-type"; + description + "The attachment circuit tag list."; + leaf tag-type { + type identityref { + base attachment-circuit-tag-type; + } + description + "The attachment circuit tag type."; + } + leaf-list value { + type string; + description + "The attachment circuit tag values. + For example, the tag may indicate + multiple VLAN identifiers."; + } + } + } + uses service-qos; + container sdp-peering { + description + "Describes SDP peering attributes."; + leaf peer-sap-id { + type string; + description + "Indicates a reference to the remote endpoints + of an attachment circuit. This information can + be used for correlation purposes, such as + identifying a service attachment point (SAP) + of a provider equipment when requesting a + service with CE based SDP attributes."; + reference + "RFC 9408: A YANG Network Data Model for + Service Attachment Points (SAPs)"; + } + container protocols { + description + "Serves as an augmentation target. + Protocols can be augmented into this container, + e.g., BGP or static routing."; + } + } + uses ac-common:service-status; + } + } + uses ac-common:service-status; + container sdp-monitoring { + config false; + description + "Container for SDP monitoring metrics."; + leaf incoming-bw-value { + type yang:gauge64; + units "bps"; + description + "Indicates the absolute value of the incoming + bandwidth at an SDP from the customer network or + from another provider's network."; + } + leaf incoming-bw-percent { + type percentage; + units "percent"; + description + "Indicates a percentage of the incoming bandwidth + at an SDP from the customer network or + from another provider's network."; + } + leaf outgoing-bw-value { + type yang:gauge64; + units "bps"; + description + "Indicates the absolute value of the outgoing + bandwidth at an SDP towards the customer network or + towards another provider's network."; + } + leaf outgoing-bw-percent { + type percentage; + units "percent"; + description + "Indicates a percentage of the outgoing bandwidth + at an SDP towards the customer network or towards + another provider's network."; + } + } + } + } + container connection-groups { + description + "Contains connection groups."; + list connection-group { + key "id"; + description + "List of connection groups."; + leaf id { + type string; + description + "The connection group identifier."; + } + leaf connectivity-type { + type identityref { + base vpn-common:vpn-topology; + } + description + "Connection group connectivity type."; + } + uses service-slo-sle-policy; + /* Per connection group SLO/SLE policy + * overrides the per Slice SLO/SLE policy. + */ + uses service-slo-sle-policy-override; + list connectivity-construct { + key "id"; + description + "List of connectivity constructs."; + leaf id { + type string; + description + "The connectivity construct identifier."; + } + choice type { + default "p2p"; + description + "Choice for connectivity construct type."; + case p2p { + description + "P2P connectivity construct."; + leaf p2p-sender-sdp { + type leafref { + path "../../../../sdps/sdp/id"; + } + description + "Reference to a sender SDP."; + } + leaf p2p-receiver-sdp { + type leafref { + path "../../../../sdps/sdp/id"; + } + description + "Reference to a receiver SDP."; + } + } + case p2mp { + description + "P2MP connectivity construct."; + leaf p2mp-sender-sdp { + type leafref { + path "../../../../sdps/sdp/id"; + } + description + "Reference to a sender SDP."; + } + leaf-list p2mp-receiver-sdp { + type leafref { + path "../../../../sdps/sdp/id"; + } + description + "Reference to a receiver SDP."; + } + } + case a2a { + description + "A2A connectivity construct."; + list a2a-sdp { + key "sdp-id"; + description + "List of included A2A SDPs."; + leaf sdp-id { + type leafref { + path "../../../../../sdps/sdp/id"; + } + description + "Reference to an SDP."; + } + uses service-slo-sle-policy; + } + } + } + uses service-slo-sle-policy; + /* Per connectivity construct SLO/SLE policy + * overrides the per slice SLO/SLE policy. + */ + uses service-slo-sle-policy-override; + uses ac-common:service-status; + container connectivity-construct-monitoring { + config false; + description + "SLO status per connectivity construct."; + uses connectivity-construct-monitoring-metrics; + } + } + container connection-group-monitoring { + config false; + description + "SLO status per connection group."; + uses connectivity-construct-monitoring-metrics; + } + } + } + container custom-topology { + description + "Serves as an augmentation target. + Container for custom topology, which is indicated by the + referenced topology predefined, e.g., an abstract RFC8345 + topology."; + uses nw:network-ref; + } + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-slice@2022-03-04.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-slice@2022-03-04.yang new file mode 100644 index 0000000000000000000000000000000000000000..b1ead4bf025c59065d01172f309af188c0ee2f75 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-slice@2022-03-04.yang @@ -0,0 +1,1130 @@ +module ietf-network-slice { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-network-slice"; + prefix ietf-ns; + + import ietf-inet-types { + prefix inet; + reference + "RFC 6991: Common YANG Types."; + } + import ietf-te-types { + prefix te-types; + reference + "RFC 8776: Common YANG Data Types for Traffic Engineering."; + } + import ietf-te-packet-types { + prefix te-packet-types; + reference + "RFC 8776: Common YANG Data Types for Traffic Engineering."; + } + + organization + "IETF Traffic Engineering Architecture and Signaling (TEAS) + Working Group"; + contact + "WG Web: <https://tools.ietf.org/wg/teas/> + WG List: <mailto:teas@ietf.org> + + Editor: Bo Wu + <lana.wubo@huawei.com> + Editor: Dhruv Dhody + <dhruv.ietf@gmail.com> + Editor: Reza Rokui + <reza.rokui@nokia.com> + Editor: Tarek Saad + <tsaad@juniper.net> + Author: Liuyan Han + <hanliuyan@chinamobile.com>"; + description + "This module contains a YANG module for the IETF Network Slice. + + Copyright (c) 2022 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Revised BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC XXXX; see the + RFC itself for full legal notices."; + + revision 2022-03-04 { + description + "initial version."; + reference + "RFC XXXX: A Yang Data Model for IETF Network Slice Operation"; + } + + /* Features */ + /* Identities */ + + identity ns-tag-type { + description + "Base identity for IETF Network Slice tag type."; + } + + identity ns-tag-customer { + base ns-tag-type; + description + "The IETF Network Slice customer ID tag type."; + } + + identity ns-tag-service { + base ns-tag-type; + description + "The IETF Network Slice service tag type."; + } + + identity ns-tag-opaque { + base ns-tag-type; + description + "The IETF Network Slice opaque tag type."; + } + + identity network-access-tag-type { + description + "Base identity for the network access tag type."; + } + + identity network-access-tag-vlan-id { + base network-access-tag-type; + description + "The network access interface VLAN ID tag type."; + } + + identity network-access-tag-ip-mask { + base network-access-tag-type; + description + "The network access tag IP mask."; + } + + identity network-access-tag-opaque { + base network-access-tag-type; + description + "The network access opaque tag type."; + } + + identity ns-isolation-type { + description + "Base identity for IETF Network slice isolation level."; + } + + identity ns-isolation-shared { + base ns-isolation-type; + description + "Shared resources (e.g. queues) are associated with the Network + Slice traffic. Hence, the IETF network slice traffic can be + impacted by effects of other services traffic sharing + the same resources."; + } + + identity ns-isolation-dedicated { + base ns-isolation-type; + description + "Dedicated resources (e.g. queues) are associated with the + Network Slice traffic. Hence, the IETF network slice traffic + is isolated from other servceis traffic sharing the same + resources."; + } + + identity ns-security-type { + description + "Base identity for for IETF Network security level."; + } + + identity ns-security-authenticate { + base ns-security-type; + description + "IETF Network Slice requires authentication."; + } + + identity ns-security-integrity { + base ns-security-type; + description + "IETF Network Slice requires data integrity."; + } + + identity ns-security-encryption { + base ns-security-type; + description + "IETF Network Slice requires data encryption."; + } + + identity ns-connectivity-type { + description + "Base identity for IETF Network Slice connectivity."; + } + + identity point-to-point { + base ns-connectivity-type; + description + "Identity for point-to-point IETF Network Slice connectivity."; + } + + identity point-to-multipoint { + base ns-connectivity-type; + description + "Identity for point-to-multipoint IETF Network Slice + connectivity."; + } + + identity multipoint-to-multipoint { + base ns-connectivity-type; + description + "Identity for multipoint-to-multipoint IETF Network Slice + connectivity."; + } + + identity any-to-any { + base ns-connectivity-type; + description + "Identity for any-to-any IETF Network Slice connectivity."; + } + + identity hub-spoke { + base ns-connectivity-type; + description + "Identity for Hub-and-Spoke IETF Network Slice connectivity."; + } + + identity custom { + base ns-connectivity-type; + description + "Identity of a custom NS topology where Hubs can act as + Spoke for certain parts of the network or Spokes as Hubs."; + } + + identity endpoint-role { + description + "Base identity of a NSE role in an IETF Network Slice topology."; + } + + identity any-to-any-role { + base endpoint-role; + description + "Identity of any-to-any NS."; + } + + identity spoke-role { + base endpoint-role; + description + "A NSE is acting as a Spoke."; + } + + identity hub-role { + base endpoint-role; + description + "A NSE is acting as a Hub."; + } + + identity ns-slo-metric-type { + description + "Base identity for IETF Network Slice SLO metric type."; + } + + identity ns-slo-one-way-bandwidth { + base ns-slo-metric-type; + description + "SLO bandwidth metric. Minimum guaranteed bandwidth between + two endpoints at any time and is measured unidirectionally."; + } + + identity ns-slo-two-way-bandwidth { + base ns-slo-metric-type; + description + "SLO bandwidth metric. Minimum guaranteed bandwidth between + two endpoints at any time."; + } + + identity ns-slo-shared-bandwidth { + base ns-slo-metric-type; + description + "The shared SLO bandwidth bound. It is the limit on the + bandwidth that can be shared amongst a group of connections + of an IETF Network Slice."; + } + + identity ns-slo-one-way-delay { + base ns-slo-metric-type; + description + "SLO one-way-delay is the upper bound of network delay when + transmitting between two endpoints. The metric is defined in + RFC7679."; + } + + identity ns-slo-two-way-delay { + base ns-slo-metric-type; + description + "SLO two-way delay is the upper bound of network delay when + transmitting between two endpoints. The metric is defined in + RFC2681."; + } + identity ns-slo-one-way-delay-variation { + base ns-slo-metric-type; + description + "SLO one-way delay variation is defined by RFC3393, is the + difference in the one-way delay between sequential packets + between two endpoints."; + } + + identity ns-slo-two-way-delay-variation { + base ns-slo-metric-type; + description + "SLO two-way delay variation is defined by RFC5481, is the + difference in the round-trip delay between sequential packets + between two endpoints."; + } + + identity ns-slo-one-way-packet-loss { + base ns-slo-metric-type; + description + "SLO loss metric. The ratio of packets dropped to packets + transmitted between two endpoints in one-way + over a period of time as specified in RFC7680."; + } + + identity ns-slo-two-way-packet-loss { + base ns-slo-metric-type; + description + "SLO loss metric. The ratio of packets dropped to packets + transmitted between two endpoints in two-way + over a period of time as specified in RFC7680."; + } + + identity ns-slo-availability { + base ns-slo-metric-type; + description + "SLO availability level."; + } + + identity ns-match-type { + description + "Base identity for IETF Network Slice traffic match type."; + } + + identity ns-phy-interface-match { + base ns-match-type; + description + "Use the physical interface as match criteria for the IETF + Network Slice traffic."; + } + + identity ns-vlan-match { + base ns-match-type; + description + "Use the VLAN ID as match criteria for the IETF Network Slice + traffic."; + } + + identity ns-label-match { + base ns-match-type; + description + "Use the MPLS label as match criteria for the IETF Network + Slice traffic."; + } + + identity peering-protocol-type { + description + "Base identity for NSE peering protocol type."; + } + + identity peering-protocol-bgp { + base peering-protocol-type; + description + "Use BGP as protocol for NSE peering with customer device."; + } + + identity peering-static-routing { + base peering-protocol-type; + description + "Use static routing for NSE peering with customer device."; + } + + /* + * Identity for availability-type + */ + + identity availability-type { + description + "Base identity from which specific availability types are + derived."; + } + + identity level-1 { + base availability-type; + description + "level 1: 99.9999%"; + } + identity level-2 { + base availability-type; + description + "level 2: 99.999%"; + } + + identity level-3 { + base availability-type; + description + "level 3: 99.99%"; + } + + identity level-4 { + base availability-type; + description + "level 4: 99.9%"; + } + + identity level-5 { + base availability-type; + description + "level 5: 99%"; + } + + /* typedef */ + + typedef operational-type { + type enumeration { + enum up { + value 0; + description + "Operational status UP."; + } + enum down { + value 1; + description + "Operational status DOWN."; + } + enum unknown { + value 2; + description + "Operational status UNKNOWN."; + } + } + description + "This is a read-only attribute used to determine the + status of a particular element."; + } + typedef ns-monitoring-type { + type enumeration { + enum one-way { + description + "Represents one-way measurments monitoring type."; + } + enum two-way { + description + "represents two-way measurements monitoring type."; + } + } + description + "An enumerated type for monitoring on a IETF Network Slice + connection."; + } + + /* Groupings */ + + grouping status-params { + description + "A grouping used to join operational and administrative status."; + container status { + description + "A container for the administrative and operational state."; + leaf admin-enabled { + type boolean; + description + "The administrative status."; + } + leaf oper-status { + type operational-type; + config false; + description + "The operational status."; + } + } + } + + grouping ns-match-criteria { + description + "A grouping for the IETF Network Slice match definition."; + container ns-match-criteria { + description + "Describes the IETF Network Slice match criteria."; + list ns-match-criterion { + key "index"; + description + "List of the IETF Network Slice traffic match criteria."; + leaf index { + type uint32; + description + "The entry index."; + } + leaf match-type { + type identityref { + base ns-match-type; + } + description + "Identifies an entry in the list of the IETF Network Slice + match criteria."; + } + list values { + key "index"; + description + "List of match criteria values."; + leaf index { + type uint8; + description + "Index of an entry in the list."; + } + leaf value { + type string; + description + "Describes the IETF Network Slice match criteria, e.g. + IP address, VLAN, etc."; + } + } + leaf target-ns-connection-group-id { + type leafref { + path "/network-slices/network-slice" + + "/ns-connection-groups/ns-connection-group" + + "/ns-connection-group-id"; + } + description + "reference to a Network Slice connection group."; + } + } + } + } + + grouping ns-sles { + description + "Indirectly Measurable Objectives of a IETF Network + Slice."; + leaf-list security { + type identityref { + base ns-security-type; + } + description + "The IETF Network Slice security SLE(s)"; + } + leaf isolation { + type identityref { + base ns-isolation-type; + } + default "ns-isolation-shared"; + description + "The IETF Network Slice isolation SLE requirement."; + } + leaf max-occupancy-level { + type uint8 { + range "1..100"; + } + description + "The maximal occupancy level specifies the number of flows to + be admitted."; + } + leaf mtu { + type uint16; + units "bytes"; + mandatory true; + description + "The MTU specifies the maximum length in octets of data + packets that can be transmitted by the NS. The value needs + to be less than or equal to the minimum MTU value of + all 'ep-network-access-points' in the NSEs of the NS."; + } + container steering-constraints { + description + "Container for the policy of steering constraints + applicable to IETF Network Slice."; + container path-constraints { + description + "Container for the policy of path constraints + applicable to IETF Network Slice."; + } + container service-function { + description + "Container for the policy of service function + applicable to IETF Network Slice."; + } + } + } + + grouping ns-metric-bounds { + description + "IETF Network Slice metric bounds grouping."; + container ns-metric-bounds { + description + "IETF Network Slice metric bounds container."; + list ns-metric-bound { + key "metric-type"; + description + "List of IETF Network Slice metric bounds."; + leaf metric-type { + type identityref { + base ns-slo-metric-type; + } + description + "Identifies an entry in the list of metric type + bounds for the IETF Network Slice."; + } + leaf metric-unit { + type string; + mandatory true; + description + "The metric unit of the parameter. For example, + s, ms, ns, and so on."; + } + leaf value-description { + type string; + description + "The description of previous value."; + } + leaf bound { + type uint64; + default "0"; + description + "The Bound on the Network Slice connection metric. A + zero indicate an unbounded upper limit for the + specific metric-type."; + } + } + } + } + + grouping ep-peering { + description + "A grouping for the IETF Network Slice Endpoint peering."; + container ep-peering { + description + "Describes NSE peering attributes."; + list protocol { + key "protocol-type"; + description + "List of the NSE peering protocol."; + leaf protocol-type { + type identityref { + base peering-protocol-type; + } + description + "Identifies an entry in the list of NSE peering + protocol type."; + } + list attribute { + key "index"; + description + "List of protocol attribute."; + leaf index { + type uint8; + description + "Index of an entry in the list."; + } + leaf attribute-description { + type string; + description + "The description of the attribute."; + } + leaf value { + type string; + description + "Describes the value of protocol attribute, e.g. + nexthop address, peer address, etc."; + } + } + } + } + } + + grouping ep-network-access-points { + description + "Grouping for the endpoint network access definition."; + container ep-network-access-points { + description + "List of network access points."; + list ep-network-access-point { + key "network-access-id"; + description + "The IETF Network Slice network access points + related parameters."; + leaf network-access-id { + type string; + description + "Uniquely identifier a network access point."; + } + leaf network-access-description { + type string; + description + "The network access point description."; + } + leaf network-access-node-id { + type string; + description + "The network access point node ID in the case of + multi-homing."; + } + leaf network-access-tp-id { + type string; + description + "The termination port ID of the EP network access + point."; + } + leaf network-access-tp-ip-address { + type inet:ip-address; + description + "The IP address of the EP network access point."; + } + leaf network-access-tp-ip-prefix-length { + type uint8; + description + "The subnet prefix length expressed in bits."; + } + leaf network-access-qos-policy-name { + type string; + description + "The name of the QoS policy that is applied to the + network access point. The name can reference a QoS + profile that is pre-provisioned on the device."; + } + leaf mtu { + type uint16; + units "bytes"; + mandatory true; + description + "Maximum size in octets of a data packet that + can traverse a NSE network access point."; + } + container network-access-tags { + description + "Container for the network access tags."; + list network-access-tag { + key "index"; + description + "The network access point tags list."; + leaf index { + type uint32; + description + "The entry index."; + } + leaf network-access-tag-type { + type identityref { + base network-access-tag-type; + } + description + "The network access point tag type."; + } + leaf network-access-tag-value { + type string; + description + "The network access point tag value."; + } + } + } + /* Per ep-network-access-point rate limits */ + uses ns-match-criteria; + uses ep-peering; + uses ns-rate-limit; + } + } + } + + grouping ep-monitoring-metrics { + description + "Grouping for the NS endpoint monitoring metrics."; + container ep-monitoring { + config false; + description + "Container for NS endpoint monitoring metrics."; + leaf incoming-utilized-bandwidth { + type te-types:te-bandwidth; + description + "Incoming bandwidth utilization at an endpoint."; + } + leaf incoming-bw-utilization { + type decimal64 { + fraction-digits 5; + range "0..100"; + } + units "percent"; + mandatory true; + description + "To be used to define the bandwidth utilization + as a percentage of the available bandwidth."; + } + leaf outgoing-utilized-bandwidth { + type te-types:te-bandwidth; + description + "Outoing bandwidth utilization at an endpoint."; + } + leaf outgoing-bw-utilization { + type decimal64 { + fraction-digits 5; + range "0..100"; + } + units "percent"; + mandatory true; + description + "To be used to define the bandwidth utilization + as a percentage of the available bandwidth."; + } + } + } + + grouping ns-connection-monitoring-metrics { + description + "Grouping for NS connection monitoring metrics."; + uses te-packet-types:one-way-performance-metrics-packet; + uses te-packet-types:two-way-performance-metrics-packet; + } + + grouping geolocation-container { + description + "A grouping containing a GPS location."; + container location { + description + "A container containing a GPS location."; + leaf altitude { + type int64; + units "millimeter"; + description + "Distance above the sea level."; + } + leaf latitude { + type decimal64 { + fraction-digits 8; + range "-90..90"; + } + description + "Relative position north or south on the Earth's surface."; + } + leaf longitude { + type decimal64 { + fraction-digits 8; + range "-180..180"; + } + description + "Angular distance east or west on the Earth's surface."; + } + } + // gps-location + } + + // geolocation-container + + grouping bw-rate-limits { + description + "Bandwidth rate limits grouping."; + reference + "RFC 7640: Traffic Management Benchmarking"; + leaf cir { + type uint64; + units "bps"; + description + "Committed Information Rate. The maximum number of bits + that a port can receive or send during one-second over an + interface."; + } + leaf cbs { + type uint64; + units "bytes"; + description + "Committed Burst Size. CBS controls the bursty nature + of the traffic. Traffic that does not use the configured + CIR accumulates credits until the credits reach the + configured CBS."; + } + leaf eir { + type uint64; + units "bps"; + description + "Excess Information Rate, i.e., excess frame delivery + allowed not subject to SLA. The traffic rate can be + limited by EIR."; + } + leaf ebs { + type uint64; + units "bytes"; + description + "Excess Burst Size. The bandwidth available for burst + traffic from the EBS is subject to the amount of + bandwidth that is accumulated during periods when + traffic allocated by the EIR policy is not used."; + } + leaf pir { + type uint64; + units "bps"; + description + "Peak Information Rate, i.e., maximum frame delivery + allowed. It is equal to or less than sum of CIR and EIR."; + } + leaf pbs { + type uint64; + units "bytes"; + description + "Peak Burst Size."; + } + } + + grouping ns-rate-limit { + description + "The rate limits grouping."; + container incoming-rate-limits { + description + "Container for the asymmetric traffic control."; + uses bw-rate-limits; + } + container outgoing-rate-limits { + description + "The rate-limit imposed on outgoing traffic."; + uses bw-rate-limits; + } + } + + grouping endpoint { + description + "IETF Network Slice endpoint related information"; + leaf ep-id { + type string; + description + "Unique identifier for the referred IETF Network + Slice endpoint."; + } + leaf ep-description { + type string; + description + "Give more description of the Network Slice endpoint."; + } + uses geolocation-container; + leaf node-id { + type string; + description + "Uniquely identifies an edge node within the IETF slice + network."; + } + leaf ep-ip { + type inet:ip-address; + description + "The IP address of the endpoint."; + } + uses ns-match-criteria; + uses ep-peering; + uses ep-network-access-points; + uses ns-rate-limit; + /* Per NSE rate limits */ + uses status-params; + uses ep-monitoring-metrics; + } + + //ns-endpoint + + grouping ns-connection { + description + "The network slice connection grouping."; + list ns-connection { + key "ns-connection-id"; + description + "List of Network Slice connections."; + leaf ns-connection-id { + type uint32; + description + "The Network Slice connection identifier."; + } + leaf ns-connectivity-type { + type identityref { + base ns-connectivity-type; + } + default "point-to-point"; + description + "Network Slice connection construct type."; + } + leaf-list src-nse { + type leafref { + path "/network-slices/network-slice" + + "/ns-endpoints/ns-endpoint/ep-id"; + } + description + "reference to source Network Slice endpoint."; + } + leaf-list dest-nse { + type leafref { + path "/network-slices/network-slice" + + "/ns-endpoints/ns-endpoint/ep-id"; + } + description + "reference to source Network Slice endpoint."; + } + uses ns-slo-sle-policy; + /* Per connection ns-slo-sle-policy overrides + * the per network slice ns-slo-sle-policy. + */ + container ns-connection-monitoring { + config false; + description + "SLO status Per NS connection."; + uses ns-connection-monitoring-metrics; + } + } + } + + //ns-connection + + grouping ns-connection-group { + description + "The Network Slice connection group is described in this + container."; + leaf ns-connection-group-id { + type string; + description + "The Network Slice connection group identifier."; + } + uses ns-slo-sle-policy; + uses ns-connection; + /* Per connection ns-slo-sle-policy overrides + * the per network slice ns-slo-sle-policy. + */ + container ns-connection-group-monitoring { + config false; + description + "SLO status Per NS connection."; + uses ns-connection-monitoring-metrics; + } + } + + //ns-connection-group + + grouping slice-template { + description + "Grouping for slice-templates."; + container ns-slo-sle-templates { + description + "Contains a set of network slice templates to + reference in the IETF network slice."; + list ns-slo-sle-template { + key "id"; + leaf id { + type string; + description + "Identification of the Service Level Objective (SLO) + and Service Level Expectation (SLE) template to be used. + Local administration meaning."; + } + leaf template-description { + type string; + description + "Description of the SLO & SLE policy template."; + } + description + "List for SLO and SLE template identifiers."; + } + } + } + + /* Configuration data nodes */ + + grouping ns-slo-sle-policy { + description + "Network Slice policy grouping."; + choice ns-slo-sle-policy { + description + "Choice for SLO and SLE policy template. + Can be standard template or customized template."; + case standard { + description + "Standard SLO template."; + leaf slo-sle-template { + type leafref { + path "/network-slices" + + "/ns-slo-sle-templates/ns-slo-sle-template/id"; + } + description + "Standard SLO and SLE template to be used."; + } + } + case custom { + description + "Customized SLO template."; + container slo-sle-policy { + description + "Contains the SLO policy."; + leaf policy-description { + type string; + description + "Description of the SLO policy."; + } + uses ns-metric-bounds; + uses ns-sles; + } + } + } + } + + container network-slices { + description + "Containes a list of IETF network slice"; + uses slice-template; + list network-slice { + key "ns-id"; + description + "A network-slice is identified by a ns-id."; + leaf ns-id { + type string; + description + "A unique network-slice identifier across an IETF NSC."; + } + leaf ns-description { + type string; + description + "Give more description of the network slice."; + } + container ns-tags { + description + "Container for the list of IETF Network Slice tags."; + list ns-tag { + key "index"; + description + "IETF Network Slice tag list."; + leaf index { + type uint32; + description + "The entry index."; + } + leaf ns-tag-type { + type identityref { + base ns-tag-type; + } + description + "The IETF Network Slice tag type."; + } + leaf ns-tag-value { + type string; + description + "The IETF Network Slice tag value."; + } + } + } + uses ns-slo-sle-policy; + uses status-params; + container ns-endpoints { + description + "NS Endpoints."; + list ns-endpoint { + key "ep-id"; + uses endpoint; + description + "List of endpoints in this slice."; + } + } + container ns-connection-groups { + description + "Contains NS connections group."; + list ns-connection-group { + key "ns-connection-group-id"; + description + "List of Network Slice connections."; + uses ns-connection-group; + } + } + } + //ietf-network-slice list + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-topology@2018-02-26.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-topology@2018-02-26.yang new file mode 100644 index 0000000000000000000000000000000000000000..1ec944d791db1da1b8236c6069f10d65b1b6f97f --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network-topology@2018-02-26.yang @@ -0,0 +1,294 @@ +module ietf-network-topology { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-network-topology"; + prefix nt; + + import ietf-inet-types { + prefix inet; + reference + "RFC 6991: Common YANG Data Types"; + } + import ietf-network { + prefix nw; + reference + "RFC 8345: A YANG Data Model for Network Topologies"; + } + + organization + "IETF I2RS (Interface to the Routing System) Working Group"; + + contact + "WG Web: <https://datatracker.ietf.org/wg/i2rs/> + WG List: <mailto:i2rs@ietf.org> + + Editor: Alexander Clemm + <mailto:ludwig@clemm.org> + + Editor: Jan Medved + <mailto:jmedved@cisco.com> + + Editor: Robert Varga + <mailto:robert.varga@pantheon.tech> + + Editor: Nitin Bahadur + <mailto:nitin_bahadur@yahoo.com> + + Editor: Hariharan Ananthakrishnan + <mailto:hari@packetdesign.com> + + Editor: Xufeng Liu + <mailto:xufeng.liu.ietf@gmail.com>"; + + description + "This module defines a common base model for a network topology, + augmenting the base network data model with links to connect + nodes, as well as termination points to terminate links + on nodes. + + Copyright (c) 2018 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8345; + see the RFC itself for full legal notices."; + + revision 2018-02-26 { + description + "Initial revision."; + reference + "RFC 8345: A YANG Data Model for Network Topologies"; + } + + typedef link-id { + type inet:uri; + description + "An identifier for a link in a topology. The precise + structure of the link-id will be up to the implementation. + The identifier SHOULD be chosen such that the same link in a + real network topology will always be identified through the + same identifier, even if the data model is instantiated in + separate datastores. An implementation MAY choose to capture + semantics in the identifier -- for example, to indicate the + type of link and/or the type of topology of which the link is + a part."; + } + + typedef tp-id { + type inet:uri; + description + "An identifier for termination points on a node. The precise + structure of the tp-id will be up to the implementation. + The identifier SHOULD be chosen such that the same termination + point in a real network topology will always be identified + through the same identifier, even if the data model is + instantiated in separate datastores. An implementation MAY + choose to capture semantics in the identifier -- for example, + to indicate the type of termination point and/or the type of + node that contains the termination point."; + } + + grouping link-ref { + description + "This grouping can be used to reference a link in a specific + network. Although it is not used in this module, it is + defined here for the convenience of augmenting modules."; + leaf link-ref { + type leafref { + path "/nw:networks/nw:network[nw:network-id=current()/../"+ + "network-ref]/nt:link/nt:link-id"; + require-instance false; + } + description + "A type for an absolute reference to a link instance. + (This type should not be used for relative references. + In such a case, a relative path should be used instead.)"; + } + uses nw:network-ref; + } + + grouping tp-ref { + description + "This grouping can be used to reference a termination point + in a specific node. Although it is not used in this module, + it is defined here for the convenience of augmenting + modules."; + leaf tp-ref { + type leafref { + path "/nw:networks/nw:network[nw:network-id=current()/../"+ + "network-ref]/nw:node[nw:node-id=current()/../"+ + "node-ref]/nt:termination-point/nt:tp-id"; + require-instance false; + } + description + "A type for an absolute reference to a termination point. + (This type should not be used for relative references. + In such a case, a relative path should be used instead.)"; + } + uses nw:node-ref; + } + + augment "/nw:networks/nw:network" { + description + "Add links to the network data model."; + list link { + key "link-id"; + description + "A network link connects a local (source) node and + a remote (destination) node via a set of the respective + node's termination points. It is possible to have several + links between the same source and destination nodes. + Likewise, a link could potentially be re-homed between + termination points. Therefore, in order to ensure that we + would always know to distinguish between links, every link + is identified by a dedicated link identifier. Note that a + link models a point-to-point link, not a multipoint link."; + leaf link-id { + type link-id; + description + "The identifier of a link in the topology. + A link is specific to a topology to which it belongs."; + } + container source { + description + "This container holds the logical source of a particular + link."; + leaf source-node { + type leafref { + path "../../../nw:node/nw:node-id"; + require-instance false; + } + description + "Source node identifier. Must be in the same topology."; + } + leaf source-tp { + type leafref { + path "../../../nw:node[nw:node-id=current()/../"+ + "source-node]/termination-point/tp-id"; + require-instance false; + } + description + "This termination point is located within the source node + and terminates the link."; + } + } + + container destination { + description + "This container holds the logical destination of a + particular link."; + leaf dest-node { + type leafref { + path "../../../nw:node/nw:node-id"; + require-instance false; + } + description + "Destination node identifier. Must be in the same + network."; + } + leaf dest-tp { + type leafref { + path "../../../nw:node[nw:node-id=current()/../"+ + "dest-node]/termination-point/tp-id"; + require-instance false; + } + description + "This termination point is located within the + destination node and terminates the link."; + } + } + list supporting-link { + key "network-ref link-ref"; + description + "Identifies the link or links on which this link depends."; + leaf network-ref { + type leafref { + path "../../../nw:supporting-network/nw:network-ref"; + require-instance false; + } + description + "This leaf identifies in which underlay topology + the supporting link is present."; + } + + leaf link-ref { + type leafref { + path "/nw:networks/nw:network[nw:network-id=current()/"+ + "../network-ref]/link/link-id"; + require-instance false; + } + description + "This leaf identifies a link that is a part + of this link's underlay. Reference loops in which + a link identifies itself as its underlay, either + directly or transitively, are not allowed."; + } + } + } + } + augment "/nw:networks/nw:network/nw:node" { + description + "Augments termination points that terminate links. + Termination points can ultimately be mapped to interfaces."; + list termination-point { + key "tp-id"; + description + "A termination point can terminate a link. + Depending on the type of topology, a termination point + could, for example, refer to a port or an interface."; + leaf tp-id { + type tp-id; + description + "Termination point identifier."; + } + list supporting-termination-point { + key "network-ref node-ref tp-ref"; + description + "This list identifies any termination points on which a + given termination point depends or onto which it maps. + Those termination points will themselves be contained + in a supporting node. This dependency information can be + inferred from the dependencies between links. Therefore, + this item is not separately configurable. Hence, no + corresponding constraint needs to be articulated. + The corresponding information is simply provided by the + implementing system."; + + leaf network-ref { + type leafref { + path "../../../nw:supporting-node/nw:network-ref"; + require-instance false; + } + description + "This leaf identifies in which topology the + supporting termination point is present."; + } + leaf node-ref { + type leafref { + path "../../../nw:supporting-node/nw:node-ref"; + require-instance false; + } + description + "This leaf identifies in which node the supporting + termination point is present."; + } + leaf tp-ref { + type leafref { + path "/nw:networks/nw:network[nw:network-id=current()/"+ + "../network-ref]/nw:node[nw:node-id=current()/../"+ + "node-ref]/termination-point/tp-id"; + require-instance false; + } + description + "Reference to the underlay node (the underlay node must + be in a different topology)."; + } + } + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network@2018-02-26.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network@2018-02-26.yang new file mode 100644 index 0000000000000000000000000000000000000000..6a03d7e41614cc8dc017cfb4d5aacfb4ca60bc2c --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-network@2018-02-26.yang @@ -0,0 +1,192 @@ +module ietf-network { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-network"; + prefix nw; + + import ietf-inet-types { + prefix inet; + reference + "RFC 6991: Common YANG Data Types"; + } + + organization + "IETF I2RS (Interface to the Routing System) Working Group"; + + contact + "WG Web: <https://datatracker.ietf.org/wg/i2rs/> + WG List: <mailto:i2rs@ietf.org> + + Editor: Alexander Clemm + <mailto:ludwig@clemm.org> + + Editor: Jan Medved + <mailto:jmedved@cisco.com> + + Editor: Robert Varga + <mailto:robert.varga@pantheon.tech> + + Editor: Nitin Bahadur + <mailto:nitin_bahadur@yahoo.com> + + Editor: Hariharan Ananthakrishnan + <mailto:hari@packetdesign.com> + + Editor: Xufeng Liu + <mailto:xufeng.liu.ietf@gmail.com>"; + description + "This module defines a common base data model for a collection + of nodes in a network. Node definitions are further used + in network topologies and inventories. + + Copyright (c) 2018 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8345; + see the RFC itself for full legal notices."; + + revision 2018-02-26 { + description + "Initial revision."; + reference + "RFC 8345: A YANG Data Model for Network Topologies"; + } + + typedef node-id { + type inet:uri; + description + "Identifier for a node. The precise structure of the node-id + will be up to the implementation. For example, some + implementations MAY pick a URI that includes the network-id + as part of the path. The identifier SHOULD be chosen + such that the same node in a real network topology will + always be identified through the same identifier, even if + the data model is instantiated in separate datastores. An + implementation MAY choose to capture semantics in the + identifier -- for example, to indicate the type of node."; + } + + typedef network-id { + type inet:uri; + description + "Identifier for a network. The precise structure of the + network-id will be up to the implementation. The identifier + SHOULD be chosen such that the same network will always be + identified through the same identifier, even if the data model + is instantiated in separate datastores. An implementation MAY + choose to capture semantics in the identifier -- for example, + to indicate the type of network."; + } + + grouping network-ref { + description + "Contains the information necessary to reference a network -- + for example, an underlay network."; + leaf network-ref { + type leafref { + path "/nw:networks/nw:network/nw:network-id"; + require-instance false; + } + description + "Used to reference a network -- for example, an underlay + network."; + } + } + + grouping node-ref { + description + "Contains the information necessary to reference a node."; + leaf node-ref { + type leafref { + path "/nw:networks/nw:network[nw:network-id=current()/../"+ + "network-ref]/nw:node/nw:node-id"; + require-instance false; + } + description + "Used to reference a node. + Nodes are identified relative to the network that + contains them."; + } + uses network-ref; + } + + container networks { + description + "Serves as a top-level container for a list of networks."; + list network { + key "network-id"; + description + "Describes a network. + A network typically contains an inventory of nodes, + topological information (augmented through the + network-topology data model), and layering information."; + leaf network-id { + type network-id; + description + "Identifies a network."; + } + container network-types { + description + "Serves as an augmentation target. + The network type is indicated through corresponding + presence containers augmented into this container."; + } + list supporting-network { + key "network-ref"; + description + "An underlay network, used to represent layered network + topologies."; + leaf network-ref { + type leafref { + path "/nw:networks/nw:network/nw:network-id"; + require-instance false; + } + description + "References the underlay network."; + } + } + + list node { + key "node-id"; + description + "The inventory of nodes of this network."; + leaf node-id { + type node-id; + description + "Uniquely identifies a node within the containing + network."; + } + list supporting-node { + key "network-ref node-ref"; + description + "Represents another node that is in an underlay network + and that supports this node. Used to represent layering + structure."; + leaf network-ref { + type leafref { + path "../../../nw:supporting-network/nw:network-ref"; + require-instance false; + } + description + "References the underlay network of which the + underlay node is a part."; + } + leaf node-ref { + type leafref { + path "/nw:networks/nw:network/nw:node/nw:node-id"; + require-instance false; + } + description + "References the underlay node itself."; + } + } + } + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-packet-fields@2019-03-04.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-packet-fields@2019-03-04.yang new file mode 100644 index 0000000000000000000000000000000000000000..2fb797bd87bf4ed825f83ec788df707b94c5f68b --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-packet-fields@2019-03-04.yang @@ -0,0 +1,576 @@ +module ietf-packet-fields { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-packet-fields"; + prefix packet-fields; + + import ietf-inet-types { + prefix inet; + reference + "RFC 6991 - Common YANG Data Types."; + } + + import ietf-yang-types { + prefix yang; + reference + "RFC 6991 - Common YANG Data Types."; + } + + import ietf-ethertypes { + prefix eth; + reference + "RFC 8519 - YANG Data Model for Network Access Control + Lists (ACLs)."; + } + + organization + "IETF NETMOD (Network Modeling) Working Group."; + + contact + "WG Web: <https://datatracker.ietf.org/wg/netmod/> + WG List: netmod@ietf.org + + Editor: Mahesh Jethanandani + mjethanandani@gmail.com + Editor: Lisa Huang + huangyi_99@yahoo.com + Editor: Sonal Agarwal + sagarwal12@gmail.com + Editor: Dana Blair + dana@blairhome.com"; + + description + "This YANG module defines groupings that are used by + the ietf-access-control-list YANG module. Their usage + is not limited to ietf-access-control-list and can be + used anywhere as applicable. + + Copyright (c) 2019 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD + License set forth in Section 4.c of the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8519; see + the RFC itself for full legal notices."; + + revision 2019-03-04 { + description + "Initial version."; + reference + "RFC 8519: YANG Data Model for Network Access Control + Lists (ACLs)."; + } + + /* + * Typedefs + */ + typedef operator { + type enumeration { + enum lte { + description + "Less than or equal to."; + } + enum gte { + description + "Greater than or equal to."; + } + enum eq { + description + "Equal to."; + } + enum neq { + description + "Not equal to."; + } + } + description + "The source and destination port range definitions + can be further qualified using an operator. An + operator is needed only if the lower-port is specified + and the upper-port is not specified. The operator + therefore further qualifies the lower-port only."; + } + + /* + * Groupings + */ + grouping port-range-or-operator { + choice port-range-or-operator { + case range { + leaf lower-port { + type inet:port-number; + must '. <= ../upper-port' { + error-message + "The lower-port must be less than or equal to + the upper-port."; + } + mandatory true; + description + "Lower boundary for a port."; + } + leaf upper-port { + type inet:port-number; + mandatory true; + description + "Upper boundary for a port."; + } + } + case operator { + leaf operator { + type operator; + default "eq"; + description + "Operator to be applied on the port below."; + } + leaf port { + type inet:port-number; + mandatory true; + description + "Port number along with the operator on which to + match."; + } + } + description + "Choice of specifying a port range or a single + port along with an operator."; + } + description + "Grouping for port definitions in the form of a + choice statement."; + } + + grouping acl-ip-header-fields { + description + "IP header fields common to IPv4 and IPv6"; + reference + "RFC 791: Internet Protocol."; + + leaf dscp { + type inet:dscp; + description + "Differentiated Services Code Point."; + reference + "RFC 2474: Definition of the Differentiated Services + Field (DS Field) in the IPv4 and IPv6 + Headers."; + } + + leaf ecn { + type uint8 { + range "0..3"; + } + description + "Explicit Congestion Notification."; + reference + "RFC 3168: The Addition of Explicit Congestion + Notification (ECN) to IP."; + } + + leaf length { + type uint16; + description + "In the IPv4 header field, this field is known as the Total + Length. Total Length is the length of the datagram, measured + in octets, including internet header and data. + + In the IPv6 header field, this field is known as the Payload + Length, which is the length of the IPv6 payload, i.e., the rest + of the packet following the IPv6 header, in octets."; + reference + "RFC 791: Internet Protocol + RFC 8200: Internet Protocol, Version 6 (IPv6) Specification."; + } + leaf ttl { + type uint8; + description + "This field indicates the maximum time the datagram is allowed + to remain in the internet system. If this field contains the + value zero, then the datagram must be dropped. + + In IPv6, this field is known as the Hop Limit."; + reference + "RFC 791: Internet Protocol + RFC 8200: Internet Protocol, Version 6 (IPv6) Specification."; + } + leaf protocol { + type uint8; + description + "Internet Protocol number. Refers to the protocol of the + payload. In IPv6, this field is known as 'next-header', + and if extension headers are present, the protocol is + present in the 'upper-layer' header."; + reference + "RFC 791: Internet Protocol + RFC 8200: Internet Protocol, Version 6 (IPv6) Specification."; + } + } + + grouping acl-ipv4-header-fields { + description + "Fields in the IPv4 header."; + leaf ihl { + type uint8 { + range "5..60"; + } + description + "In an IPv4 header field, the Internet Header Length (IHL) is + the length of the internet header in 32-bit words and + thus points to the beginning of the data. Note that the + minimum value for a correct header is 5."; + } + leaf flags { + type bits { + bit reserved { + position 0; + description + "Reserved. Must be zero."; + } + bit fragment { + position 1; + description + "Setting the value to 0 indicates may fragment, while + setting the value to 1 indicates do not fragment."; + } + bit more { + position 2; + description + "Setting the value to 0 indicates this is the last fragment, + and setting the value to 1 indicates more fragments are + coming."; + } + } + description + "Bit definitions for the Flags field in the IPv4 header."; + } + leaf offset { + type uint16 { + range "20..65535"; + } + description + "The fragment offset is measured in units of 8 octets (64 bits). + The first fragment has offset zero. The length is 13 bits"; + } + leaf identification { + type uint16; + description + "An identifying value assigned by the sender to aid in + assembling the fragments of a datagram."; + } + + choice destination-network { + case destination-ipv4-network { + leaf destination-ipv4-network { + type inet:ipv4-prefix; + description + "Destination IPv4 address prefix."; + } + } + description + "Choice of specifying a destination IPv4 address or + referring to a group of IPv4 destination addresses."; + } + + choice source-network { + case source-ipv4-network { + leaf source-ipv4-network { + type inet:ipv4-prefix; + description + "Source IPv4 address prefix."; + } + } + description + "Choice of specifying a source IPv4 address or + referring to a group of IPv4 source addresses."; + } + } + + grouping acl-ipv6-header-fields { + description + "Fields in the IPv6 header."; + + choice destination-network { + case destination-ipv6-network { + leaf destination-ipv6-network { + type inet:ipv6-prefix; + description + "Destination IPv6 address prefix."; + } + } + description + "Choice of specifying a destination IPv6 address + or referring to a group of IPv6 destination + addresses."; + } + + choice source-network { + case source-ipv6-network { + leaf source-ipv6-network { + type inet:ipv6-prefix; + description + "Source IPv6 address prefix."; + } + } + description + "Choice of specifying a source IPv6 address or + referring to a group of IPv6 source addresses."; + } + + leaf flow-label { + type inet:ipv6-flow-label; + description + "IPv6 Flow label."; + } + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text + Representation."; + } + + grouping acl-eth-header-fields { + description + "Fields in the Ethernet header."; + leaf destination-mac-address { + type yang:mac-address; + description + "Destination IEEE 802 Media Access Control (MAC) + address."; + } + leaf destination-mac-address-mask { + type yang:mac-address; + description + "Destination IEEE 802 MAC address mask."; + } + leaf source-mac-address { + type yang:mac-address; + description + "Source IEEE 802 MAC address."; + } + leaf source-mac-address-mask { + type yang:mac-address; + description + "Source IEEE 802 MAC address mask."; + } + leaf ethertype { + type eth:ethertype; + description + "The Ethernet Type (or Length) value represented + in the canonical order defined by IEEE 802. + The canonical representation uses lowercase + characters."; + reference + "IEEE 802-2014, Clause 9.2."; + } + reference + "IEEE 802: IEEE Standard for Local and Metropolitan + Area Networks: Overview and Architecture."; + } + + grouping acl-tcp-header-fields { + description + "Collection of TCP header fields that can be used to + set up a match filter."; + leaf sequence-number { + type uint32; + description + "Sequence number that appears in the packet."; + } + leaf acknowledgement-number { + type uint32; + description + "The acknowledgement number that appears in the + packet."; + } + leaf data-offset { + type uint8 { + range "5..15"; + } + description + "Specifies the size of the TCP header in 32-bit + words. The minimum size header is 5 words and + the maximum is 15 words; thus, this gives a + minimum size of 20 bytes and a maximum of 60 + bytes, allowing for up to 40 bytes of options + in the header."; + } + leaf reserved { + type uint8; + description + "Reserved for future use."; + } + leaf flags { + type bits { + bit cwr { + position 1; + description + "The Congestion Window Reduced (CWR) flag is set + by the sending host to indicate that it received + a TCP segment with the ECN-Echo (ECE) flag set + and had responded in the congestion control + mechanism."; + reference + "RFC 3168: The Addition of Explicit Congestion + Notification (ECN) to IP."; + } + bit ece { + position 2; + description + "ECN-Echo has a dual role, depending on the value + of the SYN flag. It indicates the following: if + the SYN flag is set (1), the TCP peer is ECN + capable, and if the SYN flag is clear (0), a packet + with the Congestion Experienced flag set (ECN=11) + in the IP header was received during normal + transmission (added to the header by RFC 3168). + This serves as an indication of network congestion + (or impending congestion) to the TCP sender."; + reference + "RFC 3168: The Addition of Explicit Congestion + Notification (ECN) to IP."; + } + bit urg { + position 3; + description + "Indicates that the Urgent Pointer field is significant."; + } + bit ack { + position 4; + description + "Indicates that the Acknowledgement field is significant. + All packets after the initial SYN packet sent by the + client should have this flag set."; + } + bit psh { + position 5; + description + "Push function. Asks to push the buffered data to the + receiving application."; + } + bit rst { + position 6; + description + "Reset the connection."; + } + bit syn { + position 7; + description + "Synchronize sequence numbers. Only the first packet + sent from each end should have this flag set. Some + other flags and fields change meaning based on this + flag, and some are only valid for when it is set, + and others when it is clear."; + } + bit fin { + position 8; + description + "Last package from the sender."; + } + } + description + "Also known as Control Bits. Contains nine 1-bit flags."; + reference + "RFC 793: Transmission Control Protocol."; + } + leaf window-size { + type uint16; + units "bytes"; + description + "The size of the receive window, which specifies + the number of window size units beyond the segment + identified by the sequence number in the Acknowledgement + field that the sender of this segment is currently + willing to receive."; + } + leaf urgent-pointer { + type uint16; + description + "This field is an offset from the sequence number + indicating the last urgent data byte."; + } + leaf options { + type binary { + length "1..40"; + } + description + "The length of this field is determined by the + Data Offset field. Options have up to three + fields: Option-Kind (1 byte), Option-Length + (1 byte), and Option-Data (variable). The Option-Kind + field indicates the type of option and is the + only field that is not optional. Depending on + what kind of option we are dealing with, + the next two fields may be set: the Option-Length + field indicates the total length of the option, + and the Option-Data field contains the value of + the option, if applicable."; + } + } + + grouping acl-udp-header-fields { + description + "Collection of UDP header fields that can be used + to set up a match filter."; + leaf length { + type uint16; + description + "A field that specifies the length in bytes of + the UDP header and UDP data. The minimum + length is 8 bytes because that is the length of + the header. The field size sets a theoretical + limit of 65,535 bytes (8-byte header plus 65,527 + bytes of data) for a UDP datagram. However, the + actual limit for the data length, which is + imposed by the underlying IPv4 protocol, is + 65,507 bytes (65,535 minus 8-byte UDP header + minus 20-byte IP header). + + In IPv6 jumbograms, it is possible to have + UDP packets of a size greater than 65,535 bytes. + RFC 2675 specifies that the Length field is set + to zero if the length of the UDP header plus + UDP data is greater than 65,535."; + } + } + + grouping acl-icmp-header-fields { + description + "Collection of ICMP header fields that can be + used to set up a match filter."; + leaf type { + type uint8; + description + "Also known as control messages."; + reference + "RFC 792: Internet Control Message Protocol + RFC 4443: Internet Control Message Protocol (ICMPv6) + for Internet Protocol Version 6 (IPv6) + Specification."; + } + leaf code { + type uint8; + description + "ICMP subtype. Also known as control messages."; + reference + "RFC 792: Internet Control Message Protocol + RFC 4443: Internet Control Message Protocol (ICMPv6) + for Internet Protocol Version 6 (IPv6) + Specification."; + } + leaf rest-of-header { + type binary; + description + "Unbounded in length, the contents vary based on the + ICMP type and code. Also referred to as 'Message Body' + in ICMPv6."; + reference + "RFC 792: Internet Control Message Protocol + RFC 4443: Internet Control Message Protocol (ICMPv6) + for Internet Protocol Version 6 (IPv6) + Specification."; + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-routing-types@2017-12-04.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-routing-types@2017-12-04.yang new file mode 100644 index 0000000000000000000000000000000000000000..24319c155fb104e20bee79e5b257317b01323197 --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-routing-types@2017-12-04.yang @@ -0,0 +1,771 @@ +module ietf-routing-types { + namespace "urn:ietf:params:xml:ns:yang:ietf-routing-types"; + prefix rt-types; + + import ietf-yang-types { + prefix yang; + } + import ietf-inet-types { + prefix inet; + } + + organization + "IETF RTGWG - Routing Area Working Group"; + contact + "WG Web: <https://datatracker.ietf.org/wg/rtgwg/> + WG List: <mailto:rtgwg@ietf.org> + + Editors: Xufeng Liu + <mailto:Xufeng_Liu@jabail.com> + Yingzhen Qu + <mailto:yingzhen.qu@huawei.com> + Acee Lindem + <mailto:acee@cisco.com> + Christian Hopps + <mailto:chopps@chopps.org> + Lou Berger + <mailto:lberger@labn.com>"; + + description + "This module contains a collection of YANG data types + considered generally useful for routing protocols. + + Copyright (c) 2017 IETF Trust and the persons + identified as authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8294; see + the RFC itself for full legal notices."; + revision 2017-12-04 { + description "Initial revision."; + reference + "RFC 8294: Common YANG Data Types for the Routing Area. + Section 3."; + } + + /*** Identities related to MPLS/GMPLS ***/ + + identity mpls-label-special-purpose-value { + description + "Base identity for deriving identities describing + special-purpose Multiprotocol Label Switching (MPLS) label + values."; + reference + "RFC 7274: Allocating and Retiring Special-Purpose MPLS + Labels."; + } + + identity ipv4-explicit-null-label { + base mpls-label-special-purpose-value; + description + "This identity represents the IPv4 Explicit NULL Label."; + reference + "RFC 3032: MPLS Label Stack Encoding. Section 2.1."; + } + + identity router-alert-label { + base mpls-label-special-purpose-value; + description + "This identity represents the Router Alert Label."; + reference + "RFC 3032: MPLS Label Stack Encoding. Section 2.1."; + } + + identity ipv6-explicit-null-label { + base mpls-label-special-purpose-value; + description + "This identity represents the IPv6 Explicit NULL Label."; + reference + "RFC 3032: MPLS Label Stack Encoding. Section 2.1."; + } + + identity implicit-null-label { + base mpls-label-special-purpose-value; + description + "This identity represents the Implicit NULL Label."; + reference + "RFC 3032: MPLS Label Stack Encoding. Section 2.1."; + } + + identity entropy-label-indicator { + base mpls-label-special-purpose-value; + description + "This identity represents the Entropy Label Indicator."; + reference + "RFC 6790: The Use of Entropy Labels in MPLS Forwarding. + Sections 3 and 10.1."; + } + + identity gal-label { + base mpls-label-special-purpose-value; + description + "This identity represents the Generic Associated Channel + (G-ACh) Label (GAL)."; + reference + "RFC 5586: MPLS Generic Associated Channel. + Sections 4 and 10."; + } + + identity oam-alert-label { + base mpls-label-special-purpose-value; + description + "This identity represents the OAM Alert Label."; + reference + "RFC 3429: Assignment of the 'OAM Alert Label' for + Multiprotocol Label Switching Architecture (MPLS) + Operation and Maintenance (OAM) Functions. + Sections 3 and 6."; + } + + identity extension-label { + base mpls-label-special-purpose-value; + description + "This identity represents the Extension Label."; + reference + "RFC 7274: Allocating and Retiring Special-Purpose MPLS + Labels. Sections 3.1 and 5."; + } + + /*** Collection of types related to routing ***/ + + typedef router-id { + type yang:dotted-quad; + description + "A 32-bit number in the dotted-quad format assigned to each + router. This number uniquely identifies the router within + an Autonomous System."; + } + + /*** Collection of types related to VPNs ***/ + + typedef route-target { + type string { + pattern + '(0:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|' + + '6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0):(429496729[0-5]|' + + '42949672[0-8][0-9]|' + + '4294967[01][0-9]{2}|429496[0-6][0-9]{3}|' + + '42949[0-5][0-9]{4}|' + + '4294[0-8][0-9]{5}|429[0-3][0-9]{6}|' + + '42[0-8][0-9]{7}|4[01][0-9]{8}|' + + '[1-3][0-9]{9}|[1-9][0-9]{0,8}|0))|' + + '(1:((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|' + + '25[0-5])\.){3}([0-9]|[1-9][0-9]|' + + '1[0-9]{2}|2[0-4][0-9]|25[0-5])):(6553[0-5]|' + + '655[0-2][0-9]|' + + '65[0-4][0-9]{2}|6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))|' + + '(2:(429496729[0-5]|42949672[0-8][0-9]|' + + '4294967[01][0-9]{2}|' + + '429496[0-6][0-9]{3}|42949[0-5][0-9]{4}|' + + '4294[0-8][0-9]{5}|' + + '429[0-3][0-9]{6}|42[0-8][0-9]{7}|4[01][0-9]{8}|' + + '[1-3][0-9]{9}|[1-9][0-9]{0,8}|0):' + + '(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|' + + '6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))|' + + '(6(:[a-fA-F0-9]{2}){6})|' + + '(([3-57-9a-fA-F]|[1-9a-fA-F][0-9a-fA-F]{1,3}):' + + '[0-9a-fA-F]{1,12})'; + } + + description + "A Route Target is an 8-octet BGP extended community + initially identifying a set of sites in a BGP VPN + (RFC 4364). However, it has since taken on a more general + role in BGP route filtering. A Route Target consists of two + or three fields: a 2-octet Type field, an administrator + field, and, optionally, an assigned number field. + + According to the data formats for types 0, 1, 2, and 6 as + defined in RFC 4360, RFC 5668, and RFC 7432, the encoding + pattern is defined as: + + 0:2-octet-asn:4-octet-number + 1:4-octet-ipv4addr:2-octet-number + 2:4-octet-asn:2-octet-number + 6:6-octet-mac-address + + Additionally, a generic pattern is defined for future + Route Target types: + + 2-octet-other-hex-number:6-octet-hex-number + + Some valid examples are 0:100:100, 1:1.1.1.1:100, + 2:1234567890:203, and 6:26:00:08:92:78:00."; + reference + "RFC 4360: BGP Extended Communities Attribute. + RFC 4364: BGP/MPLS IP Virtual Private Networks (VPNs). + RFC 5668: 4-Octet AS Specific BGP Extended Community. + RFC 7432: BGP MPLS-Based Ethernet VPN."; + } + + typedef ipv6-route-target { + type string { + pattern + '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])))' + + ':' + + '(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|' + + '6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0)'; + pattern '((([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?))' + + ':' + + '(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|' + + '6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0)'; + } + description + "An IPv6 Route Target is a 20-octet BGP IPv6 Address + Specific Extended Community serving the same function + as a standard 8-octet Route Target, except that it only + allows an IPv6 address as the global administrator. + The format is <ipv6-address:2-octet-number>. + + Two valid examples are 2001:db8::1:6544 and + 2001:db8::5eb1:791:6b37:17958."; + reference + "RFC 5701: IPv6 Address Specific BGP Extended Community + Attribute."; + } + + typedef route-target-type { + type enumeration { + enum import { + value 0; + description + "The Route Target applies to route import."; + } + enum export { + value 1; + description + "The Route Target applies to route export."; + } + + enum both { + value 2; + description + "The Route Target applies to both route import and + route export."; + } + } + description + "Indicates the role a Route Target takes in route filtering."; + reference + "RFC 4364: BGP/MPLS IP Virtual Private Networks (VPNs)."; + } + + typedef route-distinguisher { + type string { + pattern + '(0:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|' + + '6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0):(429496729[0-5]|' + + '42949672[0-8][0-9]|' + + '4294967[01][0-9]{2}|429496[0-6][0-9]{3}|' + + '42949[0-5][0-9]{4}|' + + '4294[0-8][0-9]{5}|429[0-3][0-9]{6}|' + + '42[0-8][0-9]{7}|4[01][0-9]{8}|' + + '[1-3][0-9]{9}|[1-9][0-9]{0,8}|0))|' + + '(1:((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|' + + '25[0-5])\.){3}([0-9]|[1-9][0-9]|' + + '1[0-9]{2}|2[0-4][0-9]|25[0-5])):(6553[0-5]|' + + '655[0-2][0-9]|' + + '65[0-4][0-9]{2}|6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))|' + + '(2:(429496729[0-5]|42949672[0-8][0-9]|' + + '4294967[01][0-9]{2}|' + + '429496[0-6][0-9]{3}|42949[0-5][0-9]{4}|' + + '4294[0-8][0-9]{5}|' + + '429[0-3][0-9]{6}|42[0-8][0-9]{7}|4[01][0-9]{8}|' + + '[1-3][0-9]{9}|[1-9][0-9]{0,8}|0):' + + '(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|' + + '6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))|' + + '(6(:[a-fA-F0-9]{2}){6})|' + + '(([3-57-9a-fA-F]|[1-9a-fA-F][0-9a-fA-F]{1,3}):' + + '[0-9a-fA-F]{1,12})'; + } + + description + "A Route Distinguisher is an 8-octet value used to + distinguish routes from different BGP VPNs (RFC 4364). + A Route Distinguisher will have the same format as a + Route Target as per RFC 4360 and will consist of + two or three fields: a 2-octet Type field, an administrator + field, and, optionally, an assigned number field. + + According to the data formats for types 0, 1, 2, and 6 as + defined in RFC 4360, RFC 5668, and RFC 7432, the encoding + pattern is defined as: + + 0:2-octet-asn:4-octet-number + 1:4-octet-ipv4addr:2-octet-number + 2:4-octet-asn:2-octet-number + 6:6-octet-mac-address + + Additionally, a generic pattern is defined for future + route discriminator types: + + 2-octet-other-hex-number:6-octet-hex-number + + Some valid examples are 0:100:100, 1:1.1.1.1:100, + 2:1234567890:203, and 6:26:00:08:92:78:00."; + reference + "RFC 4360: BGP Extended Communities Attribute. + RFC 4364: BGP/MPLS IP Virtual Private Networks (VPNs). + RFC 5668: 4-Octet AS Specific BGP Extended Community. + RFC 7432: BGP MPLS-Based Ethernet VPN."; + } + + typedef route-origin { + type string { + pattern + '(0:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|' + + '6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0):(429496729[0-5]|' + + '42949672[0-8][0-9]|' + + '4294967[01][0-9]{2}|429496[0-6][0-9]{3}|' + + '42949[0-5][0-9]{4}|' + + '4294[0-8][0-9]{5}|429[0-3][0-9]{6}|' + + '42[0-8][0-9]{7}|4[01][0-9]{8}|' + + '[1-3][0-9]{9}|[1-9][0-9]{0,8}|0))|' + + '(1:((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|' + + '25[0-5])\.){3}([0-9]|[1-9][0-9]|' + + '1[0-9]{2}|2[0-4][0-9]|25[0-5])):(6553[0-5]|' + + '655[0-2][0-9]|' + + '65[0-4][0-9]{2}|6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))|' + + '(2:(429496729[0-5]|42949672[0-8][0-9]|' + + '4294967[01][0-9]{2}|' + + '429496[0-6][0-9]{3}|42949[0-5][0-9]{4}|' + + '4294[0-8][0-9]{5}|' + + '429[0-3][0-9]{6}|42[0-8][0-9]{7}|4[01][0-9]{8}|' + + '[1-3][0-9]{9}|[1-9][0-9]{0,8}|0):' + + '(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|' + + '6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0))|' + + '(6(:[a-fA-F0-9]{2}){6})|' + + '(([3-57-9a-fA-F]|[1-9a-fA-F][0-9a-fA-F]{1,3}):' + + '[0-9a-fA-F]{1,12})'; + } + description + "A Route Origin is an 8-octet BGP extended community + identifying the set of sites where the BGP route + originated (RFC 4364). A Route Origin will have the same + format as a Route Target as per RFC 4360 and will consist + of two or three fields: a 2-octet Type field, an + administrator field, and, optionally, an assigned number + field. + + According to the data formats for types 0, 1, 2, and 6 as + defined in RFC 4360, RFC 5668, and RFC 7432, the encoding + pattern is defined as: + + 0:2-octet-asn:4-octet-number + 1:4-octet-ipv4addr:2-octet-number + 2:4-octet-asn:2-octet-number + 6:6-octet-mac-address + Additionally, a generic pattern is defined for future + Route Origin types: + + 2-octet-other-hex-number:6-octet-hex-number + + Some valid examples are 0:100:100, 1:1.1.1.1:100, + 2:1234567890:203, and 6:26:00:08:92:78:00."; + reference + "RFC 4360: BGP Extended Communities Attribute. + RFC 4364: BGP/MPLS IP Virtual Private Networks (VPNs). + RFC 5668: 4-Octet AS Specific BGP Extended Community. + RFC 7432: BGP MPLS-Based Ethernet VPN."; + } + + typedef ipv6-route-origin { + type string { + pattern + '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])))' + + ':' + + '(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|' + + '6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0)'; + pattern '((([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?))' + + ':' + + '(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|' + + '6[0-4][0-9]{3}|' + + '[1-5][0-9]{4}|[1-9][0-9]{0,3}|0)'; + } + description + "An IPv6 Route Origin is a 20-octet BGP IPv6 Address + Specific Extended Community serving the same function + as a standard 8-octet route, except that it only allows + an IPv6 address as the global administrator. The format + is <ipv6-address:2-octet-number>. + + Two valid examples are 2001:db8::1:6544 and + 2001:db8::5eb1:791:6b37:17958."; + reference + "RFC 5701: IPv6 Address Specific BGP Extended Community + Attribute."; + } + + /*** Collection of types common to multicast ***/ + + typedef ipv4-multicast-group-address { + type inet:ipv4-address { + pattern '(2((2[4-9])|(3[0-9]))\.).*'; + } + description + "This type represents an IPv4 multicast group address, + which is in the range of 224.0.0.0 to 239.255.255.255."; + reference + "RFC 1112: Host Extensions for IP Multicasting."; + } + + typedef ipv6-multicast-group-address { + type inet:ipv6-address { + pattern '(([fF]{2}[0-9a-fA-F]{2}):).*'; + } + description + "This type represents an IPv6 multicast group address, + which is in the range of ff00::/8."; + reference + "RFC 4291: IP Version 6 Addressing Architecture. Section 2.7. + RFC 7346: IPv6 Multicast Address Scopes."; + } + + typedef ip-multicast-group-address { + type union { + type ipv4-multicast-group-address; + type ipv6-multicast-group-address; + } + description + "This type represents a version-neutral IP multicast group + address. The format of the textual representation implies + the IP version."; + } + + typedef ipv4-multicast-source-address { + type union { + type enumeration { + enum * { + description + "Any source address."; + } + } + type inet:ipv4-address; + } + description + "Multicast source IPv4 address type."; + } + + typedef ipv6-multicast-source-address { + type union { + type enumeration { + enum * { + description + "Any source address."; + } + } + type inet:ipv6-address; + } + description + "Multicast source IPv6 address type."; + } + + /*** Collection of types common to protocols ***/ + + typedef bandwidth-ieee-float32 { + type string { + pattern + '0[xX](0((\.0?)?[pP](\+)?0?|(\.0?))|' + + '1(\.([0-9a-fA-F]{0,5}[02468aAcCeE]?)?)?[pP](\+)?(12[0-7]|' + + '1[01][0-9]|0?[0-9]?[0-9])?)'; + } + description + "Bandwidth in IEEE 754 floating-point 32-bit binary format: + (-1)**(S) * 2**(Exponent-127) * (1 + Fraction), + where Exponent uses 8 bits and Fraction uses 23 bits. + The units are octets per second. + The encoding format is the external hexadecimal-significant + character sequences specified in IEEE 754 and ISO/IEC C99. + The format is restricted to be normalized, non-negative, and + non-fraction: 0x1.hhhhhhp{+}d, 0X1.HHHHHHP{+}D, or 0x0p0, + where 'h' and 'H' are hexadecimal digits and 'd' and 'D' are + integers in the range of [0..127]. + When six hexadecimal digits are used for 'hhhhhh' or + 'HHHHHH', the least significant digit must be an even + number. 'x' and 'X' indicate hexadecimal; 'p' and 'P' + indicate a power of two. Some examples are 0x0p0, 0x1p10, + and 0x1.abcde2p+20."; + reference + "IEEE Std 754-2008: IEEE Standard for Floating-Point + Arithmetic. + ISO/IEC C99: Information technology - Programming + Languages - C."; + } + + typedef link-access-type { + type enumeration { + enum broadcast { + description + "Specify broadcast multi-access network."; + } + enum non-broadcast-multiaccess { + description + "Specify Non-Broadcast Multi-Access (NBMA) network."; + } + enum point-to-multipoint { + description + "Specify point-to-multipoint network."; + } + enum point-to-point { + description + "Specify point-to-point network."; + } + } + description + "Link access type."; + } + + typedef timer-multiplier { + type uint8; + description + "The number of timer value intervals that should be + interpreted as a failure."; + } + + typedef timer-value-seconds16 { + type union { + type uint16 { + range "1..65535"; + } + type enumeration { + enum infinity { + description + "The timer is set to infinity."; + } + enum not-set { + description + "The timer is not set."; + } + } + } + units "seconds"; + description + "Timer value type, in seconds (16-bit range)."; + } + + typedef timer-value-seconds32 { + type union { + type uint32 { + range "1..4294967295"; + } + type enumeration { + enum infinity { + description + "The timer is set to infinity."; + } + enum not-set { + description + "The timer is not set."; + } + } + } + units "seconds"; + description + "Timer value type, in seconds (32-bit range)."; + } + + typedef timer-value-milliseconds { + type union { + type uint32 { + range "1..4294967295"; + } + type enumeration { + enum infinity { + description + "The timer is set to infinity."; + } + enum not-set { + description + "The timer is not set."; + } + } + } + units "milliseconds"; + description + "Timer value type, in milliseconds."; + } + + typedef percentage { + type uint8 { + range "0..100"; + } + description + "Integer indicating a percentage value."; + } + + typedef timeticks64 { + type uint64; + description + "This type is based on the timeticks type defined in + RFC 6991, but with 64-bit width. It represents the time, + modulo 2^64, in hundredths of a second between two epochs."; + reference + "RFC 6991: Common YANG Data Types."; + } + + typedef uint24 { + type uint32 { + range "0..16777215"; + } + description + "24-bit unsigned integer."; + } + + /*** Collection of types related to MPLS/GMPLS ***/ + + typedef generalized-label { + type binary; + description + "Generalized Label. Nodes sending and receiving the + Generalized Label are aware of the link-specific + label context and type."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description. Section 3.2."; + } + + typedef mpls-label-special-purpose { + type identityref { + base mpls-label-special-purpose-value; + } + description + "This type represents the special-purpose MPLS label values."; + reference + "RFC 3032: MPLS Label Stack Encoding. + RFC 7274: Allocating and Retiring Special-Purpose MPLS + Labels."; + } + + typedef mpls-label-general-use { + type uint32 { + range "16..1048575"; + } + description + "The 20-bit label value in an MPLS label stack as specified + in RFC 3032. This label value does not include the + encodings of Traffic Class and TTL (Time to Live). + The label range specified by this type is for general use, + with special-purpose MPLS label values excluded."; + reference + "RFC 3032: MPLS Label Stack Encoding."; + } + + typedef mpls-label { + type union { + type mpls-label-special-purpose; + type mpls-label-general-use; + } + description + "The 20-bit label value in an MPLS label stack as specified + in RFC 3032. This label value does not include the + encodings of Traffic Class and TTL."; + reference + "RFC 3032: MPLS Label Stack Encoding."; + } + + /*** Groupings **/ + + grouping mpls-label-stack { + description + "This grouping specifies an MPLS label stack. The label + stack is encoded as a list of label stack entries. The + list key is an identifier that indicates the relative + ordering of each entry, with the lowest-value identifier + corresponding to the top of the label stack."; + container mpls-label-stack { + description + "Container for a list of MPLS label stack entries."; + list entry { + key "id"; + description + "List of MPLS label stack entries."; + leaf id { + type uint8; + description + "Identifies the entry in a sequence of MPLS label + stack entries. An entry with a smaller identifier + value precedes an entry with a larger identifier + value in the label stack. The value of this ID has + no semantic meaning other than relative ordering + and referencing the entry."; + } + leaf label { + type rt-types:mpls-label; + description + "Label value."; + } + + leaf ttl { + type uint8; + description + "Time to Live (TTL)."; + reference + "RFC 3032: MPLS Label Stack Encoding."; + } + leaf traffic-class { + type uint8 { + range "0..7"; + } + description + "Traffic Class (TC)."; + reference + "RFC 5462: Multiprotocol Label Switching (MPLS) Label + Stack Entry: 'EXP' Field Renamed to 'Traffic Class' + Field."; + } + } + } + } + + grouping vpn-route-targets { + description + "A grouping that specifies Route Target import-export rules + used in BGP-enabled VPNs."; + reference + "RFC 4364: BGP/MPLS IP Virtual Private Networks (VPNs). + RFC 4664: Framework for Layer 2 Virtual Private Networks + (L2VPNs)."; + list vpn-target { + key "route-target"; + description + "List of Route Targets."; + leaf route-target { + type rt-types:route-target; + description + "Route Target value."; + } + leaf route-target-type { + type rt-types:route-target-type; + mandatory true; + description + "Import/export type of the Route Target."; + } + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-te-packet-types@2024-10-30.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-te-packet-types@2024-10-30.yang new file mode 100644 index 0000000000000000000000000000000000000000..70bead463820694c7f88c977f8ef28df0bb3db7a --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-te-packet-types@2024-10-30.yang @@ -0,0 +1,806 @@ +module ietf-te-packet-types { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-te-packet-types"; + prefix te-packet-types; + + import ietf-yang-types { + prefix yang; + reference + "RFC 6991: Common YANG Data Types"; + } + + import ietf-te-types { + prefix te-types; + reference + "RFC XXXX: Common YANG Data Types for Traffic Engineering"; + } + // RFC Editor: replace XXXX with actual RFC number + // and remove this note + + organization + "IETF Traffic Engineering Architecture and Signaling (TEAS) + Working Group"; + contact + "WG Web: <https://datatracker.ietf.org/wg/teas/> + WG List: <mailto:teas@ietf.org> + + Editor: Tarek Saad + <mailto:tsaad.net@gmail.com> + + Editor: Rakesh Gandhi + <mailto:rgandhi@cisco.com> + + Editor: Vishnu Pavan Beeram + <mailto:vbeeram@juniper.net> + + Editor: Xufeng Liu + <mailto:xufeng.liu.ietf@gmail.com> + + Editor: Igor Bryskin + <mailto:i_bryskin@yahoo.com>"; + description + "This YANG module contains a collection of generally useful YANG + data type definitions specific to Packet Traffic Enginnering + (TE). + + The model fully conforms to the Network Management Datastore + Architecture (NMDA). + + The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL + NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', + 'MAY', and 'OPTIONAL' in this document are to be interpreted as + described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, + they appear in all capitals, as shown here. + + Copyright (c) 2024 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject to + the license terms contained in, the Revised BSD License set + forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC XXXX + (https://www.rfc-editor.org/info/rfcXXXX); see the RFC itself + for full legal notices."; + revision 2024-10-30 { + description + "This revision adds the following new identities: + - bandwidth-profile-type; + - link-metric-delay-variation; + - link-metric-loss; + - path-metric-delay-variation; + - path-metric-loss. + + This revision adds the following new groupings: + - bandwidth-profile-parameters; + - te-packet-path-bandwidth; + - te-packet-link-bandwidth. + + This revision provides also few editorial changes."; + reference + "RFC XXXX: Common YANG Data Types for Traffic Engineering"; + } + // RFC Editor: replace XXXX with actual RFC number, update date + // information and remove this note + + revision 2020-06-10 { + description + "Latest revision of TE MPLS types."; + reference + "RFC 8776: Common YANG Data Types for Traffic Engineering"; + } + + /* + * Identities + */ + + identity bandwidth-profile-type { + description + "Bandwidth Profile Types"; + } + + identity mef-10 { + base bandwidth-profile-type; + description + "MEF 10 Bandwidth Profile"; + reference + "MEF 10.3: Ethernet Services Attributes Phase 3"; + } + + identity rfc-2697 { + base bandwidth-profile-type; + description + "RFC 2697 Bandwidth Profile"; + reference + "RFC 2697: A Single Rate Three Color Marker"; + } + + identity rfc-2698 { + base bandwidth-profile-type; + description + "RFC 2698 Bandwidth Profile"; + reference + "RFC 2698: A Two Rate Three Color Marker"; + } + + // Derived identities from te-types:link-metric-type + + identity link-metric-delay-variation { + base te-types:link-metric-type; + description + "The Unidirectional Delay Variation Metric, + measured in units of microseconds."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric Extensions, + Section 4.3 + RFC 8570: IS-IS Traffic Engineering (TE) Metric Extensions, + Section 4.3"; + } + + identity link-metric-loss { + base te-types:link-metric-type; + description + "The Unidirectional Link Loss Metric, + measured in units of 0.000003%."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric Extensions, + Section 4.4 + RFC 8570: IS-IS Traffic Engineering (TE) Metric Extensions, + Section 4.4"; + } + + // Derived identities from te-types:link-metric-type + + identity path-metric-delay-variation { + base te-types:path-metric-type; + description + "The Path Delay Variation Metric, + measured in units of microseconds."; + reference + "RFC 8233: Extensions to the Path Computation Element + Communication Protocol (PCEP) to Compute + Service-Aware Label Switched Paths (LSPs), + Section 3.1.2"; + } + + identity path-metric-loss { + base te-types:path-metric-type; + description + "The Path Loss Metric, measured in units of 0.000003%."; + reference + "RFC 8233: Extensions to the Path Computation Element + Communication Protocol (PCEP) to Compute + Service-Aware Label Switched Paths (LSPs), + Section 3.1.3"; + } + + /* + * Typedefs + */ + + typedef te-bandwidth-requested-type { + type enumeration { + enum specified-value { + description + "Bandwidth value is explicitly specified."; + } + enum specified-profile { + description + "Bandwidth profile is explicitly specified."; + } + enum auto { + description + "Bandwidth is automatically computed."; + } + } + description + "Enumerated type for specifying whether bandwidth is + explicitly specified or automatically computed."; + } + + typedef te-class-type { + type uint8; + description + "Diffserv-TE Class-Type. Defines a set of Traffic Trunks + crossing a link that is governed by a specific set of + bandwidth constraints. Class-Type is used for the purposes + of link bandwidth allocation, constraint-based routing, and + admission control."; + reference + "RFC 4124: Protocol Extensions for Support of Diffserv-aware + MPLS Traffic Engineering"; + } + + typedef bc-type { + type uint8 { + range "0..7"; + } + description + "Diffserv-TE bandwidth constraints as defined in RFC 4124."; + reference + "RFC 4124: Protocol Extensions for Support of Diffserv-aware + MPLS Traffic Engineering"; + } + + typedef bandwidth-kbps { + type uint64; + units "Kbps"; + description + "Bandwidth values, expressed in kilobits per second."; + } + + typedef bandwidth-mbps { + type uint64; + units "Mbps"; + description + "Bandwidth values, expressed in megabits per second."; + } + + typedef bandwidth-gbps { + type uint64; + units "Gbps"; + description + "Bandwidth values, expressed in gigabits per second."; + } + + identity backup-protection-type { + description + "Base identity for the backup protection type."; + } + + identity backup-protection-link { + base backup-protection-type; + description + "Backup provides link protection only."; + } + + identity backup-protection-node-link { + base backup-protection-type; + description + "Backup offers node (preferred) or link protection."; + } + + identity bc-model-type { + description + "Base identity for the Diffserv-TE Bandwidth Constraints + Model type."; + reference + "RFC 4124: Protocol Extensions for Support of Diffserv-aware + MPLS Traffic Engineering"; + } + + identity bc-model-rdm { + base bc-model-type; + description + "Russian Dolls Bandwidth Constraints Model type."; + reference + "RFC 4127: Russian Dolls Bandwidth Constraints Model for + Diffserv-aware MPLS Traffic Engineering"; + } + + identity bc-model-mam { + base bc-model-type; + description + "Maximum Allocation Bandwidth Constraints Model type."; + reference + "RFC 4125: Maximum Allocation Bandwidth Constraints Model for + Diffserv-aware MPLS Traffic Engineering"; + } + + identity bc-model-mar { + base bc-model-type; + description + "Maximum Allocation with Reservation Bandwidth Constraints + Model type."; + reference + "RFC 4126: Max Allocation with Reservation Bandwidth + Constraints Model for Diffserv-aware MPLS Traffic + Engineering & Performance Comparisons"; + } + + /* + * Groupings + */ + + grouping performance-metrics-attributes-packet { + description + "Contains PM attributes."; + uses te-types:performance-metrics-attributes { + augment "performance-metrics-one-way" { + leaf one-way-min-delay { + type uint32 { + range "0..16777215"; + } + description + "One-way minimum delay or latency in microseconds."; + } + leaf one-way-min-delay-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "One-way minimum delay or latency normality."; + } + leaf one-way-max-delay { + type uint32 { + range "0..16777215"; + } + description + "One-way maximum delay or latency in microseconds."; + } + leaf one-way-max-delay-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "One-way maximum delay or latency normality."; + } + leaf one-way-delay-variation { + type uint32 { + range "0..16777215"; + } + description + "One-way delay variation in microseconds."; + reference + "RFC 5481: Packet Delay Variation Applicability + Statement, Section 4.2"; + } + leaf one-way-delay-variation-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "One-way delay variation normality."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric + Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions + RFC 7823: Performance-Based Path Selection for + Explicitly Routed Label Switched Paths (LSPs) + Using TE Metric Extensions"; + } + leaf one-way-packet-loss { + type decimal64 { + fraction-digits 6; + range "0..50.331642"; + } + description + "One-way packet loss as a percentage of the total traffic + sent over a configurable interval. The finest precision + is 0.000003%, where the maximum is 50.331642%."; + reference + "RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions, Section 4.4"; + } + leaf one-way-packet-loss-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "Packet loss normality."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric + Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions + RFC 7823: Performance-Based Path Selection for + Explicitly Routed Label Switched Paths (LSPs) + Using TE Metric Extensions"; + } + description + "PM one-way packet-specific augmentation for a generic PM + grouping."; + } + augment "performance-metrics-two-way" { + leaf two-way-min-delay { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "Two-way minimum delay or latency in microseconds."; + } + leaf two-way-min-delay-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "Two-way minimum delay or latency normality."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric + Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions + RFC 7823: Performance-Based Path Selection for + Explicitly Routed Label Switched Paths (LSPs) + Using TE Metric Extensions"; + } + leaf two-way-max-delay { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "Two-way maximum delay or latency in microseconds."; + } + leaf two-way-max-delay-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "Two-way maximum delay or latency normality."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric + Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions + RFC 7823: Performance-Based Path Selection for + Explicitly Routed Label Switched Paths (LSPs) + Using TE Metric Extensions"; + } + leaf two-way-delay-variation { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "Two-way delay variation in microseconds."; + reference + "RFC 5481: Packet Delay Variation Applicability + Statement, Section 4.2"; + } + leaf two-way-delay-variation-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "Two-way delay variation normality."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric + Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions + RFC 7823: Performance-Based Path Selection for + Explicitly Routed Label Switched Paths (LSPs) + Using TE Metric Extensions"; + } + leaf two-way-packet-loss { + type decimal64 { + fraction-digits 6; + range "0..50.331642"; + } + default "0"; + description + "Two-way packet loss as a percentage of the total traffic + sent over a configurable interval. The finest precision + is 0.000003%."; + } + leaf two-way-packet-loss-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "Two-way packet loss normality."; + } + description + "PM two-way packet-specific augmentation for a generic PM + grouping."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric Extensions + RFC 7823: Performance-Based Path Selection for Explicitly + Routed Label Switched Paths (LSPs) Using TE + Metric Extensions"; + } + } + } + + grouping one-way-performance-metrics-packet { + description + "One-way packet PM throttle grouping."; + leaf one-way-min-delay { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "One-way minimum delay or latency in microseconds."; + } + leaf one-way-max-delay { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "One-way maximum delay or latency in microseconds."; + } + leaf one-way-delay-variation { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "One-way delay variation in microseconds."; + } + leaf one-way-packet-loss { + type decimal64 { + fraction-digits 6; + range "0..50.331642"; + } + default "0"; + description + "One-way packet loss as a percentage of the total traffic + sent over a configurable interval. The finest precision is + 0.000003%."; + } + } + + grouping one-way-performance-metrics-gauge-packet { + description + "One-way packet PM throttle grouping. + + This grouping is used to report the same metrics defined in + the one-way-performance-metrics-packet grouping, using gauges + instead of uint32 data types and referencing IPPM RFCs + instead of IGP-TE RFCs."; + leaf one-way-min-delay { + type yang:gauge64; + description + "One-way minimum delay or latency in microseconds."; + } + leaf one-way-max-delay { + type yang:gauge64; + description + "One-way maximum delay or latency in microseconds."; + reference + "RFC 7679: A One-Way Delay Metric for IP Performance + Metrics (IPPM)"; + } + leaf one-way-delay-variation { + type yang:gauge64; + description + "One-way delay variation in microseconds."; + reference + "RFC 3393: IP Packet Delay Variation Metric for IP + Performance Metrics (IPPM)"; + } + leaf one-way-packet-loss { + type decimal64 { + fraction-digits 5; + range "0..100"; + } + description + "The ratio of packets dropped to packets transmitted between + two endpoints."; + reference + "RFC 7680: A One-Way Loss Metric for IP Performance + Metrics (IPPM)"; + } + } + + grouping two-way-performance-metrics-packet { + description + "Two-way packet PM throttle grouping."; + leaf two-way-min-delay { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "Two-way minimum delay or latency in microseconds."; + } + leaf two-way-max-delay { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "Two-way maximum delay or latency in microseconds."; + } + leaf two-way-delay-variation { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "Two-way delay variation in microseconds."; + } + leaf two-way-packet-loss { + type decimal64 { + fraction-digits 6; + range "0..50.331642"; + } + default "0"; + description + "Two-way packet loss as a percentage of the total traffic + sent over a configurable interval. The finest precision is + 0.000003%."; + } + } + + grouping two-way-performance-metrics-gauge-packet { + description + "Two-way packet PM throttle grouping. + + This grouping is used to report the same metrics defined in + the two-way-performance-metrics-packet grouping, using gauges + instead of uint32 data types and referencing IPPM RFCs + instead of IGP-TE RFCs."; + leaf two-way-min-delay { + type yang:gauge64; + description + "Two-way minimum delay or latency in microseconds."; + reference + "RFC 2681: A Round-trip Delay Metric for IPPM"; + } + leaf two-way-max-delay { + type yang:gauge64; + description + "Two-way maximum delay or latency in microseconds."; + reference + "RFC 2681: A Round-trip Delay Metric for IPPM"; + } + leaf two-way-delay-variation { + type yang:gauge64; + description + "Two-way delay variation in microseconds."; + reference + "RFC 5481: Packet Delay Variation Applicability Statement"; + } + leaf two-way-packet-loss { + type decimal64 { + fraction-digits 5; + range "0..100"; + } + description + "The ratio of packets dropped to packets transmitted between + two endpoints."; + } + } + + grouping performance-metrics-throttle-container-packet { + description + "Packet PM threshold grouping."; + uses te-types:performance-metrics-throttle-container { + augment "throttle/threshold-out" { + uses one-way-performance-metrics-packet; + uses two-way-performance-metrics-packet; + description + "PM threshold-out packet augmentation for a + generic grouping."; + } + augment "throttle/threshold-in" { + uses one-way-performance-metrics-packet; + uses two-way-performance-metrics-packet; + description + "PM threshold-in packet augmentation for a + generic grouping."; + } + augment "throttle/threshold-accelerated-advertisement" { + uses one-way-performance-metrics-packet; + uses two-way-performance-metrics-packet; + description + "PM accelerated advertisement packet augmentation for a + generic grouping."; + } + } + } + + grouping bandwidth-profile-parameters { + description + "Common parameters to define bandwidth profiles in packet + networks."; + leaf cir { + type uint64; + units "bits/second"; + description + "Committed Information Rate (CIR)."; + } + leaf cbs { + type uint64; + units "bytes"; + description + "Committed Burst Size (CBS)."; + } + leaf eir { + type uint64; + units "bits/second"; + description + "Excess Information Rate (EIR)."; + } + leaf ebs { + type uint64; + units "bytes"; + description + "Excess Burst Size (EBS)."; + } + leaf pir { + type uint64; + units "bits/second"; + description + "Peak Information Rate (PIR)."; + } + leaf pbs { + type uint64; + units "bytes"; + description + "Peak Burst Size (PBS)."; + } + } + + grouping te-packet-path-bandwidth { + description + "Bandwidth attributes for TE Packet paths."; + container packet-bandwidth { + description + "Bandwidth attributes for TE Packet paths."; + leaf specification-type { + type te-bandwidth-requested-type; + description + "The bandwidth specification type, either explicitly + specified or automatically computed."; + } + leaf set-bandwidth { + when "../specification-type = 'specified-value'" { + description + "When the bandwidth value is explicitly specified."; + } + type bandwidth-kbps; + description + "Set the bandwidth value explicitly, e.g., using offline + calculation."; + } + container bandwidth-profile { + when "../specification-type = 'specified-profile'" { + description + "When the bandwidth profile is explicitly specified."; + } + description + "Set the bandwidth profile attributes explicitly."; + leaf bandwidth-profile-name { + type string; + description + "Name of Bandwidth Profile."; + } + leaf bandwidth-profile-type { + type identityref { + base bandwidth-profile-type; + } + description + "Type of Bandwidth Profile."; + } + uses bandwidth-profile-parameters; + } + leaf class-type { + type te-types:te-ds-class; + description + "The Class-Type of traffic transported by the LSP."; + reference + "RFC 4124: Protocol Extensions for Support of + Diffserv-aware MPLS Traffic Engineering, + Section 4.3.1"; + } + leaf signaled-bandwidth { + type te-packet-types:bandwidth-kbps; + config false; + description + "The currently signaled bandwidth of the LSP. + + In the case where the bandwidth is specified + explicitly, then this will match the value of the + set-bandwidth leaf. + + In the cases where the bandwidth is dynamically + computed by the system, the current value of the + bandwidth should be reflected."; + } + } + } + + grouping te-packet-link-bandwidth { + description + "Bandwidth attributes for Packet TE links."; + leaf packet-bandwidth { + type uint64; + units "bits/second"; + description + "Bandwidth value for Packet TE links."; + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-te-types@2024-10-30.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-te-types@2024-10-30.yang new file mode 100644 index 0000000000000000000000000000000000000000..5d9ae16f4bb43b5389217771a9b3f83d177449ca --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-te-types@2024-10-30.yang @@ -0,0 +1,4399 @@ +module ietf-te-types { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-te-types"; + prefix te-types; + + import ietf-inet-types { + prefix inet; + reference + "RFC 6991: Common YANG Data Types"; + } + import ietf-yang-types { + prefix yang; + reference + "RFC 6991: Common YANG Data Types"; + } + import ietf-routing-types { + prefix rt-types; + reference + "RFC 8294: Common YANG Data Types for the Routing Area"; + } + + import ietf-network { + prefix "nw"; + reference + "RFC 8345: A YANG Data Model for Network Topologies"; + } + + import ietf-network-topology { + prefix "nt"; + reference + "RFC 8345: A YANG Data Model for Network Topologies"; + } + + organization + "IETF Traffic Engineering Architecture and Signaling (TEAS) + Working Group"; + contact + "WG Web: <https://datatracker.ietf.org/wg/teas/> + WG List: <mailto:teas@ietf.org> + + Editor: Tarek Saad + <mailto:tsaad.net@gmail.com> + + Editor: Rakesh Gandhi + <mailto:rgandhi@cisco.com> + + Editor: Vishnu Pavan Beeram + <mailto:vbeeram@juniper.net> + + Editor: Xufeng Liu + <mailto:xufeng.liu.ietf@gmail.com> + + Editor: Igor Bryskin + <mailto:i_bryskin@yahoo.com>"; + description + "This YANG module contains a collection of generally useful + YANG data type definitions specific to TE. The model fully + conforms to the Network Management Datastore Architecture + (NMDA). + + The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL + NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', + 'MAY', and 'OPTIONAL' in this document are to be interpreted as + described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, + they appear in all capitals, as shown here. + + Copyright (c) 2024 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject to + the license terms contained in, the Revised BSD License set + forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC XXXX + (https://www.rfc-editor.org/info/rfcXXXX); see the RFC itself + for full legal notices."; + revision 2024-10-30 { + description + "This revision adds the following new identities: + - lsp-provisioning-error-reason; + - association-type-diversity; + - tunnel-admin-state-auto; + - lsp-restoration-restore-none; + - restoration-scheme-rerouting; + - path-metric-optimization-type; + - link-path-metric-type; + - link-metric-type and its derived identities; + - path-computation-error-reason and its derived identities; + - protocol-origin-type and its derived identities; + - svec-objective-function-type and its derived identities; + - svec-metric-type and its derived identities. + + This revision adds the following new data types: + - path-type. + + This revision adds the following new groupings: + - encoding-and-switching-type; + - te-generic-node-id. + + This revision updates the following identities: + - objective-function-type; + - action-exercise; + - path-metric-type; + - path-metric-te; + - path-metric-igp; + - path-metric-hop; + - path-metric-delay-average; + - path-metric-delay-minimum; + - path-metric-residual-bandwidth; + - path-metric-optimize-includes; + - path-metric-optimize-excludes; + - te-optimization-criterion. + + This revision updates the following data types: + - te-node-id. + + This revision updates the following groupings: + - explicit-route-hop: + - adds the following leaves: + - node-id-uri; + - link-tp-id-uri; + - updates the following leaves: + - node-id; + - link-tp-id; + - record-route-state: + - adds the following leaves: + - node-id-uri; + - link-tp-id-uri; + - updates the following leaves: + - node-id; + - link-tp-id; + - optimization-metric-entry: + - updates the following leaves: + - metric-type; + - tunnel-constraints; + - adds the following leaves: + - network-id; + - path-constraints-route-objects: + - updates the following containers: + - explicit-route-objects-always; + - generic-path-metric-bounds: + - updates the following leaves: + - metric-type; + - generic-path-optimization + - adds the following leaves: + - tiebreaker; + - deprecate the following containers: + - tiebreakers. + + This revision obsoletes the following identities: + - of-minimize-agg-bandwidth-consumption; + - of-minimize-load-most-loaded-link; + - of-minimize-cost-path-set; + - lsp-protection-reroute-extra; + - lsp-protection-reroute. + + This revision provides also few editorial changes."; + reference + "RFC XXXX: Common YANG Data Types for Traffic Engineering"; + } + // RFC Editor: replace XXXX with actual RFC number, update date + // information and remove this note + + revision 2020-06-10 { + description + "Initial Version of TE types."; + reference + "RFC 8776: Common YANG Data Types for Traffic Engineering"; + } + + /** + * Typedefs + */ + + typedef admin-group { + type yang:hex-string { + /* 01:02:03:04 */ + length "1..11"; + } + description + "Administrative group / resource class / color representation + in 'hex-string' type. + + The most significant byte in the hex-string is the farthest + to the left in the byte sequence. Leading zero bytes in the + configured value may be omitted for brevity."; + reference + "RFC 3630: Traffic Engineering (TE) Extensions to OSPF + Version 2 + RFC 5305: IS-IS Extensions for Traffic Engineering + RFC 7308: Extended Administrative Groups in MPLS Traffic + Engineering (MPLS-TE)"; + } + + typedef admin-groups { + type union { + type admin-group; + type extended-admin-group; + } + description + "Derived types for TE administrative groups."; + } + + typedef extended-admin-group { + type yang:hex-string; + description + "Extended administrative group / resource class / color + representation in 'hex-string' type. + + The most significant byte in the hex-string is the farthest + to the left in the byte sequence. Leading zero bytes in the + configured value may be omitted for brevity."; + reference + "RFC 7308: Extended Administrative Groups in MPLS Traffic + Engineering (MPLS-TE)"; + } + + typedef path-attribute-flags { + type union { + type identityref { + base session-attributes-flags; + } + type identityref { + base lsp-attributes-flags; + } + } + description + "Path attributes flags type."; + } + + typedef performance-metrics-normality { + type enumeration { + enum unknown { + value 0; + description + "Unknown."; + } + enum normal { + value 1; + description + "Normal. Indicates that the anomalous bit is not set."; + } + enum abnormal { + value 2; + description + "Abnormal. Indicates that the anomalous bit is set."; + } + } + description + "Indicates whether a performance metric is normal (anomalous + bit not set), abnormal (anomalous bit set), or unknown."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric Extensions + RFC 7823: Performance-Based Path Selection for Explicitly + Routed Label Switched Paths (LSPs) Using TE Metric + Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric Extensions"; + } + + typedef srlg { + type uint32; + description + "SRLG type."; + reference + "RFC 4203: OSPF Extensions in Support of Generalized + Multi-Protocol Label Switching (GMPLS) + RFC 5307: IS-IS Extensions in Support of Generalized + Multi-Protocol Label Switching (GMPLS)"; + } + + typedef te-common-status { + type enumeration { + enum up { + description + "Enabled."; + } + enum down { + description + "Disabled."; + } + enum testing { + description + "In some test mode."; + } + enum preparing-maintenance { + description + "The resource is disabled in the control plane to prepare + for a graceful shutdown for maintenance purposes."; + reference + "RFC 5817: Graceful Shutdown in MPLS and Generalized MPLS + Traffic Engineering Networks"; + } + enum maintenance { + description + "The resource is disabled in the data plane for maintenance + purposes."; + } + enum unknown { + description + "Status is unknown."; + } + } + description + "Defines a type representing the common states of a TE + resource."; + } + + typedef te-bandwidth { + type string { + pattern '0[xX](0((\.0?)?[pP](\+)?0?|(\.0?))|' + + '1(\.([\da-fA-F]{0,5}[02468aAcCeE]?)?)?' + + '[pP](\+)?(12[0-7]|' + + '1[01]\d|0?\d?\d)?)|0[xX][\da-fA-F]{1,8}|\d+' + + '(,(0[xX](0((\.0?)?[pP](\+)?0?|(\.0?))|' + + '1(\.([\da-fA-F]{0,5}[02468aAcCeE]?)?)?' + + '[pP](\+)?(12[0-7]|' + + '1[01]\d|0?\d?\d)?)|0[xX][\da-fA-F]{1,8}|\d+))*'; + } + description + "This is the generic bandwidth type. It is a string containing + a list of numbers separated by commas, where each of these + numbers can be non-negative decimal, hex integer, or + hex float: + + (dec | hex | float)[*(','(dec | hex | float))] + + For the packet-switching type, the string encoding follows + the type 'bandwidth-ieee-float32' as defined in RFC 8294 + (e.g., 0x1p10), where the units are in bytes per second. + + For the Optical Transport Network (OTN) switching type, + a list of integers can be used, such as '0,2,3,1', indicating + two ODU0s and one ODU3. ('ODU' stands for 'Optical Data + Unit'.) For Dense Wavelength Division Multiplexing (DWDM), + a list of pairs of slot numbers and widths can be used, + such as '0,2,3,3', indicating a frequency slot 0 with + slot width 2 and a frequency slot 3 with slot width 3. + Canonically, the string is represented as all lowercase and in + hex, where the prefix '0x' precedes the hex number."; + reference + "RFC 8294: Common YANG Data Types for the Routing Area + ITU-T G.709: Interfaces for the optical transport network - + Edition 6.0 (06/2020)"; + } + + typedef te-ds-class { + type uint8 { + range "0..7"; + } + description + "The Differentiated Services Class-Type of traffic."; + reference + "RFC 4124: Protocol Extensions for Support of Diffserv-aware + MPLS Traffic Engineering, Section 4.3.1"; + } + + typedef te-global-id { + type uint32; + description + "An identifier to uniquely identify an operator, which can be + either a provider or a client. + + The definition of this type is taken from RFCs 6370 and 5003. + + This attribute type is used solely to provide a globally + unique context for TE topologies."; + reference + "RFC 5003: Attachment Individual Identifier (AII) Types for + Aggregation + RFC 6370: MPLS Transport Profile (MPLS-TP) Identifiers"; + } + + typedef te-hop-type { + type enumeration { + enum loose { + description + "A loose hop in an explicit path."; + } + enum strict { + description + "A strict hop in an explicit path."; + } + } + description + "Enumerated type for specifying loose or strict paths."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels, + Section 4.3.3"; + } + + typedef te-link-access-type { + type enumeration { + enum point-to-point { + description + "The link is point-to-point."; + } + enum multi-access { + description + "The link is multi-access, including broadcast and NBMA."; + } + } + description + "Defines a type representing the access type of a TE link."; + reference + "RFC 3630: Traffic Engineering (TE) Extensions to OSPF + Version 2"; + } + + typedef te-label-direction { + type enumeration { + enum forward { + description + "Label allocated for the forward LSP direction."; + } + enum reverse { + description + "Label allocated for the reverse LSP direction."; + } + } + description + "Enumerated type for specifying the forward or reverse + label."; + } + + typedef te-link-direction { + type enumeration { + enum incoming { + description + "The explicit route represents an incoming link on + a node."; + } + enum outgoing { + description + "The explicit route represents an outgoing link on + a node."; + } + } + description + "Enumerated type for specifying the direction of a link on + a node."; + } + + typedef te-metric { + type uint32; + description + "TE metric."; + reference + "RFC 3785: Use of Interior Gateway Protocol (IGP) Metric as a + second MPLS Traffic Engineering (TE) Metric"; + } + + typedef te-node-id { + type union { + type yang:dotted-quad; + type inet:ipv6-address-no-zone; + } + description + "A type representing the identifier for a node in a TE + topology. + + The identifier is represented either as 4 octets in + dotted-quad notation, or as 16 octets in full, mixed, + shortened, or shortened-mixed IPv6 address notation. + + This attribute MAY be mapped to the Router Address TLV + described in Section 2.4.1 of RFC 3630, the TE Router ID + described in Section 3 of RFC 6827, the Traffic Engineering + Router ID TLV described in Section 4.3 of RFC 5305, the TE + Router ID TLV described in Section 3.2.1 of RFC 6119, or the + IPv6 TE Router ID TLV described in Section 4.1 of RFC 6119. + + The reachability of such a TE node MAY be achieved by a + mechanism such as that described in Section 6.2 of RFC 6827."; + reference + "RFC 3630: Traffic Engineering (TE) Extensions to OSPF + Version 2, Section 2.4.1 + RFC 5305: IS-IS Extensions for Traffic Engineering, + Section 4.3 + RFC 6119: IPv6 Traffic Engineering in IS-IS, Section 3.2.1 + RFC 6827: Automatically Switched Optical Network (ASON) + Routing for OSPFv2 Protocols, Section 3"; + } + + typedef te-oper-status { + type te-common-status; + description + "Defines a type representing the operational status of + a TE resource."; + } + + typedef te-admin-status { + type te-common-status; + description + "Defines a type representing the administrative status of + a TE resource."; + } + + typedef te-path-disjointness { + type bits { + bit node { + position 0; + description + "Node disjoint."; + } + bit link { + position 1; + description + "Link disjoint."; + } + bit srlg { + position 2; + description + "SRLG (Shared Risk Link Group) disjoint."; + } + } + description + "Type of the resource disjointness for a TE tunnel path."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + typedef te-recovery-status { + type enumeration { + enum normal { + description + "Both the recovery span and the working span are fully + allocated and active, data traffic is being + transported over (or selected from) the working + span, and no trigger events are reported."; + } + enum recovery-started { + description + "The recovery action has been started but not completed."; + } + enum recovery-succeeded { + description + "The recovery action has succeeded. The working span has + reported a failure/degrade condition, and the user traffic + is being transported (or selected) on the recovery span."; + } + enum recovery-failed { + description + "The recovery action has failed."; + } + enum reversion-started { + description + "The reversion has started."; + } + enum reversion-succeeded { + description + "The reversion action has succeeded."; + } + enum reversion-failed { + description + "The reversion has failed."; + } + enum recovery-unavailable { + description + "The recovery is unavailable, as a result of either an + operator's lockout command or a failure condition + detected on the recovery span."; + } + enum recovery-admin { + description + "The operator has issued a command to switch the user + traffic to the recovery span."; + } + enum wait-to-restore { + description + "The recovery domain is recovering from a failure/degrade + condition on the working span that is being controlled by + the Wait-to-Restore (WTR) timer."; + } + } + description + "Defines the status of a recovery action."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + typedef te-template-name { + type string { + pattern '/?([a-zA-Z0-9\-_.]+)(/[a-zA-Z0-9\-_.]+)*'; + } + description + "A type for the name of a TE node template or TE link + template."; + } + + typedef te-topology-event-type { + type enumeration { + enum add { + value 0; + description + "A TE node or TE link has been added."; + } + enum remove { + value 1; + description + "A TE node or TE link has been removed."; + } + enum update { + value 2; + description + "A TE node or TE link has been updated."; + } + } + description + "TE event type for notifications."; + } + + typedef te-topology-id { + type union { + type string { + length "0"; + // empty string + } + type string { + pattern '([a-zA-Z0-9\-_.]+:)*' + + '/?([a-zA-Z0-9\-_.]+)(/[a-zA-Z0-9\-_.]+)*'; + } + } + description + "An identifier for a topology. + + It is optional to have one or more prefixes at the beginning, + separated by colons. The prefixes can be 'network-types' as + defined in the 'ietf-network' module in RFC 8345, to help the + user better understand the topology before further inquiry + is made."; + reference + "RFC 8345: A YANG Data Model for Network Topologies"; + } + + typedef te-tp-id { + type union { + type uint32; + // Unnumbered + type inet:ip-address; + // IPv4 or IPv6 address + } + description + "An identifier for a TE link endpoint on a node. + + This attribute is mapped to a local or remote link identifier + as defined in RFCs 3630 and 5305."; + reference + "RFC 3630: Traffic Engineering (TE) Extensions to OSPF + Version 2 + RFC 5305: IS-IS Extensions for Traffic Engineering"; + } + + typedef path-type { + type enumeration { + enum primary-path { + description + "Indicates that the TE path is a primary path."; + } + enum secondary-path { + description + "Indicates that the TE path is a secondary path."; + } + enum primary-reverse-path { + description + "Indicates that the TE path is a primary reverse path."; + } + enum secondary-reverse-path { + description + "Indicates that the TE path is a secondary reverse path."; + } + } + description + "The type of TE path, indicating whether a path is a primary, + or a reverse primary, or a secondary, or a reverse secondary + path."; + } + + /* TE features */ + + feature p2mp-te { + description + "Indicates support for Point-to-Multipoint TE (P2MP-TE)."; + reference + "RFC 4875: Extensions to Resource Reservation Protocol - + Traffic Engineering (RSVP-TE) for + Point-to-Multipoint TE Label Switched Paths (LSPs)"; + } + + feature frr-te { + description + "Indicates support for TE Fast Reroute (FRR)."; + reference + "RFC 4090: Fast Reroute Extensions to RSVP-TE for LSP Tunnels"; + } + + feature extended-admin-groups { + description + "Indicates support for TE link extended administrative + groups."; + reference + "RFC 7308: Extended Administrative Groups in MPLS Traffic + Engineering (MPLS-TE)"; + } + + feature named-path-affinities { + description + "Indicates support for named path affinities."; + } + + feature named-extended-admin-groups { + description + "Indicates support for named extended administrative groups."; + } + + feature named-srlg-groups { + description + "Indicates support for named SRLG groups."; + } + + feature named-path-constraints { + description + "Indicates support for named path constraints."; + } + + feature path-optimization-metric { + description + "Indicates support for path optimization metrics."; + } + + feature path-optimization-objective-function { + description + "Indicates support for path optimization objective functions."; + } + + /* + * Identities + */ + + identity lsp-provisioning-error-reason { + description + "Base identity for LSP provisioning errors."; + } + + identity session-attributes-flags { + description + "Base identity for the RSVP-TE session attributes flags."; + } + + identity local-protection-desired { + base session-attributes-flags; + description + "Local protection is desired."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels, + Section 4.7.1"; + } + + identity se-style-desired { + base session-attributes-flags; + description + "Shared explicit style, to allow the LSP to be established + and share resources with the old LSP."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels"; + } + + identity local-recording-desired { + base session-attributes-flags; + description + "Label recording is desired."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels, + Section 4.7.1"; + } + + identity bandwidth-protection-desired { + base session-attributes-flags; + description + "Requests FRR bandwidth protection on LSRs, if present."; + reference + "RFC 4090: Fast Reroute Extensions to RSVP-TE for LSP + Tunnels"; + } + + identity node-protection-desired { + base session-attributes-flags; + description + "Requests FRR node protection on LSRs, if present."; + reference + "RFC 4090: Fast Reroute Extensions to RSVP-TE for LSP + Tunnels"; + } + + identity path-reevaluation-request { + base session-attributes-flags; + description + "This flag indicates that a path re-evaluation (of the + current path in use) is requested. Note that this does + not trigger any LSP reroutes but instead just signals a + request to evaluate whether a preferable path exists."; + reference + "RFC 4736: Reoptimization of Multiprotocol Label Switching + (MPLS) Traffic Engineering (TE) Loosely Routed + Label Switched Path (LSP)"; + } + + identity soft-preemption-desired { + base session-attributes-flags; + description + "Soft preemption of LSP resources is desired."; + reference + "RFC 5712: MPLS Traffic Engineering Soft Preemption"; + } + + identity lsp-attributes-flags { + description + "Base identity for LSP attributes flags."; + } + + identity end-to-end-rerouting-desired { + base lsp-attributes-flags; + description + "Indicates end-to-end rerouting behavior for an LSP + undergoing establishment. This MAY also be used to + specify the behavior of end-to-end LSP recovery for + established LSPs."; + reference + "RFC 4920: Crankback Signaling Extensions for MPLS and GMPLS + RSVP-TE + RFC 5420: Encoding of Attributes for MPLS LSP Establishment + Using Resource Reservation Protocol Traffic + Engineering (RSVP-TE) + RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO)"; + } + + identity boundary-rerouting-desired { + base lsp-attributes-flags; + description + "Indicates boundary rerouting behavior for an LSP undergoing + establishment. This MAY also be used to specify + segment-based LSP recovery through nested crankback for + established LSPs. The boundary Area Border Router (ABR) / + Autonomous System Border Router (ASBR) can decide to forward + the PathErr message upstream to either an upstream boundary + ABR/ASBR or the ingress LSR. Alternatively, it can try to + select another egress boundary LSR."; + reference + "RFC 4920: Crankback Signaling Extensions for MPLS and GMPLS + RSVP-TE + RFC 5420: Encoding of Attributes for MPLS LSP Establishment + Using Resource Reservation Protocol Traffic + Engineering (RSVP-TE) + RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO)"; + } + + identity segment-based-rerouting-desired { + base lsp-attributes-flags; + description + "Indicates segment-based rerouting behavior for an LSP + undergoing establishment. This MAY also be used to specify + segment-based LSP recovery for established LSPs."; + reference + "RFC 4920: Crankback Signaling Extensions for MPLS and GMPLS + RSVP-TE + RFC 5420: Encoding of Attributes for MPLS LSP Establishment + Using Resource Reservation Protocol + Traffic Engineering (RSVP-TE) + RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO)"; + } + + identity lsp-integrity-required { + base lsp-attributes-flags; + description + "Indicates that LSP integrity is required."; + reference + "RFC 4875: Extensions to Resource Reservation Protocol - + Traffic Engineering (RSVP-TE) for + Point-to-Multipoint TE Label Switched Paths (LSPs) + RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO)"; + } + + identity contiguous-lsp-desired { + base lsp-attributes-flags; + description + "Indicates that a contiguous LSP is desired."; + reference + "RFC 5151: Inter-Domain MPLS and GMPLS Traffic Engineering -- + Resource Reservation Protocol-Traffic Engineering + (RSVP-TE) Extensions + RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO)"; + } + + identity lsp-stitching-desired { + base lsp-attributes-flags; + description + "Indicates that LSP stitching is desired."; + reference + "RFC 5150: Label Switched Path Stitching with Generalized + Multiprotocol Label Switching Traffic Engineering + (GMPLS TE) + RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO)"; + } + + identity pre-planned-lsp-flag { + base lsp-attributes-flags; + description + "Indicates that the LSP MUST be provisioned in the + control plane only."; + reference + "RFC 6001: Generalized MPLS (GMPLS) Protocol Extensions for + Multi-Layer and Multi-Region Networks (MLN/MRN) + RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO)"; + } + + identity non-php-behavior-flag { + base lsp-attributes-flags; + description + "Indicates that non-PHP (non-Penultimate Hop Popping) + behavior for the LSP is desired."; + reference + "RFC 6511: Non-Penultimate Hop Popping Behavior and + Out-of-Band Mapping for RSVP-TE Label Switched + Paths + RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO)"; + } + + identity oob-mapping-flag { + base lsp-attributes-flags; + description + "Indicates that signaling of the egress binding information + is out of band (e.g., via the Border Gateway Protocol + (BGP))."; + reference + "RFC 6511: Non-Penultimate Hop Popping Behavior and + Out-of-Band Mapping for RSVP-TE Label Switched + Paths + RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO)"; + } + + identity entropy-label-capability { + base lsp-attributes-flags; + description + "Indicates entropy label capability."; + reference + "RFC 6790: The Use of Entropy Labels in MPLS Forwarding + RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO)"; + } + + identity oam-mep-entity-desired { + base lsp-attributes-flags; + description + "OAM Maintenance Entity Group End Point (MEP) entities + desired."; + reference + "RFC 7260: GMPLS RSVP-TE Extensions for Operations, + Administration, and Maintenance (OAM) + Configuration"; + } + + identity oam-mip-entity-desired { + base lsp-attributes-flags; + description + "OAM Maintenance Entity Group Intermediate Points (MIP) + entities desired."; + reference + "RFC 7260: GMPLS RSVP-TE Extensions for Operations, + Administration, and Maintenance (OAM) + Configuration"; + } + + identity srlg-collection-desired { + base lsp-attributes-flags; + description + "SRLG collection desired."; + reference + "RFC 7570: Label Switched Path (LSP) Attribute in the + Explicit Route Object (ERO) + RFC 8001: RSVP-TE Extensions for Collecting Shared Risk + Link Group (SRLG) Information"; + } + + identity loopback-desired { + base lsp-attributes-flags; + description + "This flag indicates that a particular node on the LSP is + required to enter loopback mode. This can also be + used to specify the loopback state of the node."; + reference + "RFC 7571: GMPLS RSVP-TE Extensions for Lock Instruct and + Loopback"; + } + + identity p2mp-te-tree-eval-request { + base lsp-attributes-flags; + description + "P2MP-TE tree re-evaluation request."; + reference + "RFC 8149: RSVP Extensions for Reoptimization of Loosely + Routed Point-to-Multipoint Traffic Engineering + Label Switched Paths (LSPs)"; + } + + identity rtm-set-desired { + base lsp-attributes-flags; + description + "Residence Time Measurement (RTM) attribute flag requested."; + reference + "RFC 8169: Residence Time Measurement in MPLS Networks"; + } + + identity link-protection-type { + description + "Base identity for the link protection type."; + } + + identity link-protection-unprotected { + base link-protection-type; + description + "Unprotected link type."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity link-protection-extra-traffic { + base link-protection-type; + description + "Extra-Traffic protected link type."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity link-protection-shared { + base link-protection-type; + description + "Shared protected link type."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity link-protection-1-for-1 { + base link-protection-type; + description + "One-for-one (1:1) protected link type."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity link-protection-1-plus-1 { + base link-protection-type; + description + "One-plus-one (1+1) protected link type."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity link-protection-enhanced { + base link-protection-type; + description + "A compound link protection type derived from the underlay + TE tunnel protection configuration supporting the TE link."; + } + + identity association-type { + description + "Base identity for the tunnel association."; + } + + identity association-type-recovery { + base association-type; + description + "Association type for recovery, used to associate LSPs of the + same tunnel for recovery."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery + RFC 6780: RSVP ASSOCIATION Object Extensions"; + } + + identity association-type-resource-sharing { + base association-type; + description + "Association type for resource sharing, used to enable + resource sharing during make-before-break."; + reference + "RFC 4873: GMPLS Segment Recovery + RFC 6780: RSVP ASSOCIATION Object Extensions"; + } + + identity association-type-double-sided-bidir { + base association-type; + description + "Association type for double-sided bidirectional LSPs, + used to associate two LSPs of two tunnels that are + independently configured on either endpoint."; + reference + "RFC 7551: RSVP-TE Extensions for Associated Bidirectional + Label Switched Paths (LSPs)"; + } + + identity association-type-single-sided-bidir { + base association-type; + description + "Association type for single-sided bidirectional LSPs, + used to associate two LSPs of two tunnels, where one + tunnel is configured on one side/endpoint and the other + tunnel is dynamically created on the other endpoint."; + reference + "RFC 6780: RSVP ASSOCIATION Object Extensions + RFC 7551: RSVP-TE Extensions for Associated Bidirectional + Label Switched Paths (LSPs)"; + } + + identity association-type-diversity { + base association-type; + description + "Association Type diversity used to associate LSPs whose + paths are to be diverse from each other."; + reference + "RFC 8800: Path Computation Element Communication Protocol + (PCEP) Extension for Label Switched Path (LSP) + Diversity Constraint Signaling"; + } + + identity objective-function-type { + description + "Base identity for path objective function types."; + } + + identity of-minimize-cost-path { + base objective-function-type; + description + "Objective function for minimizing path cost."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity of-minimize-load-path { + base objective-function-type; + description + "Objective function for minimizing the load on one or more + paths."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity of-maximize-residual-bandwidth { + base objective-function-type; + description + "Objective function for maximizing residual bandwidth."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity of-minimize-agg-bandwidth-consumption { + base objective-function-type; + status obsolete; + description + "Objective function for minimizing aggregate bandwidth + consumption. + + This identity has been obsoleted: the + 'svec-of-minimize-agg-bandwidth-consumption' identity SHOULD + be used instead."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity of-minimize-load-most-loaded-link { + base objective-function-type; + status obsolete; + description + "Objective function for minimizing the load on the link that + is carrying the highest load. + + This identity has been obsoleted: the + 'svec-of-minimize-load-most-loaded-link' identity SHOULD + be used instead."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity of-minimize-cost-path-set { + base objective-function-type; + status obsolete; + description + "Objective function for minimizing the cost on a path set. + + This identity has been obsoleted: the + 'svec-of-minimize-cost-path-set' identity SHOULD + be used instead."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity path-computation-method { + description + "Base identity for supported path computation mechanisms."; + } + + identity path-locally-computed { + base path-computation-method; + description + "Indicates a constrained-path LSP in which the + path is computed by the local LER."; + reference + "RFC 9522: Overview and Principles of Internet Traffic + Engineering, Section 4.4"; + } + + identity path-externally-queried { + base path-computation-method; + description + "Constrained-path LSP in which the path is obtained by + querying an external source, such as a PCE server. + In the case that an LSP is defined to be externally queried, + it may also have associated explicit definitions (provided + to the external source to aid computation). The path that + is returned by the external source may require further local + computation on the device."; + reference + "RFC 9522: Overview and Principles of Internet Traffic + Engineering + RFC 4657: Path Computation Element (PCE) Communication + Protocol Generic Requirements"; + } + + identity path-explicitly-defined { + base path-computation-method; + description + "Constrained-path LSP in which the path is + explicitly specified as a collection of strict and/or loose + hops."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels + RFC 9522: Overview and Principles of Internet Traffic + Engineering"; + } + + identity lsp-metric-type { + description + "Base identity for the LSP metric specification types."; + } + + identity lsp-metric-relative { + base lsp-metric-type; + description + "The metric specified for the LSPs to which this identity + refers is specified as a value relative to the IGP metric + cost to the LSP's tail end."; + reference + "RFC 4657: Path Computation Element (PCE) Communication + Protocol Generic Requirements"; + } + + identity lsp-metric-absolute { + base lsp-metric-type; + description + "The metric specified for the LSPs to which this identity + refers is specified as an absolute value."; + reference + "RFC 4657: Path Computation Element (PCE) Communication + Protocol Generic Requirements"; + } + + identity lsp-metric-inherited { + base lsp-metric-type; + description + "The metric for the LSPs to which this identity refers is + not specified explicitly; rather, it is directly inherited + from the IGP cost."; + reference + "RFC 4657: Path Computation Element (PCE) Communication + Protocol Generic Requirements"; + } + + identity te-tunnel-type { + description + "Base identity from which specific tunnel types are derived."; + } + + identity te-tunnel-p2p { + base te-tunnel-type; + description + "TE Point-to-Point (P2P) tunnel type."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels"; + } + + identity te-tunnel-p2mp { + base te-tunnel-type; + description + "TE P2MP tunnel type."; + reference + "RFC 4875: Extensions to Resource Reservation Protocol - + Traffic Engineering (RSVP-TE) for + Point-to-Multipoint TE Label Switched Paths + (LSPs)"; + } + + identity tunnel-action-type { + description + "Base identity from which specific tunnel action types + are derived."; + } + + identity tunnel-action-resetup { + base tunnel-action-type; + description + "TE tunnel action that tears down the tunnel's current LSP + (if any) and attempts to re-establish a new LSP."; + } + + identity tunnel-action-reoptimize { + base tunnel-action-type; + description + "TE tunnel action that reoptimizes the placement of the + tunnel LSP(s)."; + } + + identity tunnel-action-switchpath { + base tunnel-action-type; + description + "TE tunnel action that switches the tunnel's LSP to use the + specified path."; + } + + identity te-action-result { + description + "Base identity from which specific TE action results + are derived."; + } + + identity te-action-success { + base te-action-result; + description + "TE action was successful."; + } + + identity te-action-fail { + base te-action-result; + description + "TE action failed."; + } + + identity tunnel-action-inprogress { + base te-action-result; + description + "TE action is in progress."; + } + + identity tunnel-admin-state-type { + description + "Base identity for TE tunnel administrative states."; + } + + identity tunnel-admin-state-up { + base tunnel-admin-state-type; + description + "Tunnel's administrative state is up."; + } + + identity tunnel-admin-state-down { + base tunnel-admin-state-type; + description + "Tunnel's administrative state is down."; + } + + identity tunnel-admin-state-auto { + base tunnel-admin-state-type; + description + "Tunnel administrative auto state. The administrative status + in state datastore transitions to 'tunnel-admin-up' when the + tunnel used by the client layer, and to 'tunnel-admin-down' + when it is not used by the client layer."; + } + + identity tunnel-state-type { + description + "Base identity for TE tunnel states."; + } + + identity tunnel-state-up { + base tunnel-state-type; + description + "Tunnel's state is up."; + } + + identity tunnel-state-down { + base tunnel-state-type; + description + "Tunnel's state is down."; + } + + identity lsp-state-type { + description + "Base identity for TE LSP states."; + } + + identity lsp-path-computing { + base lsp-state-type; + description + "State path computation is in progress."; + } + + identity lsp-path-computation-ok { + base lsp-state-type; + description + "State path computation was successful."; + } + + identity lsp-path-computation-failed { + base lsp-state-type; + description + "State path computation failed."; + } + + identity lsp-state-setting-up { + base lsp-state-type; + description + "State is being set up."; + } + + identity lsp-state-setup-ok { + base lsp-state-type; + description + "State setup was successful."; + } + + identity lsp-state-setup-failed { + base lsp-state-type; + description + "State setup failed."; + } + + identity lsp-state-up { + base lsp-state-type; + description + "State is up."; + } + + identity lsp-state-tearing-down { + base lsp-state-type; + description + "State is being torn down."; + } + + identity lsp-state-down { + base lsp-state-type; + description + "State is down."; + } + + identity path-invalidation-action-type { + description + "Base identity for TE path invalidation action types."; + } + + identity path-invalidation-action-drop { + base path-invalidation-action-type; + description + "Upon invalidation of the TE tunnel path, the tunnel remains + valid, but any packet mapped over the tunnel is dropped."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels, + Section 2.5"; + } + + identity path-invalidation-action-teardown { + base path-invalidation-action-type; + description + "TE path invalidation action teardown."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels, + Section 2.5"; + } + + identity lsp-restoration-type { + description + "Base identity from which LSP restoration types are derived."; + } + + identity lsp-restoration-restore-none { + base lsp-restoration-type; + description + "No LSP affected by a failure is restored."; + } + + identity lsp-restoration-restore-any { + base lsp-restoration-type; + description + "Any LSP affected by a failure is restored."; + } + + identity lsp-restoration-restore-all { + base lsp-restoration-type; + description + "Affected LSPs are restored after all LSPs of the tunnel are + broken."; + } + + identity restoration-scheme-type { + description + "Base identity for LSP restoration schemes."; + } + + identity restoration-scheme-rerouting { + base restoration-scheme-type; + description + "Restoration LSP is computed after the failure detection. + + This restoration scheme is also known as + 'Full LSP Re-routing.'"; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity restoration-scheme-preconfigured { + base restoration-scheme-type; + description + "Restoration LSP is preconfigured prior to the failure."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity restoration-scheme-precomputed { + base restoration-scheme-type; + description + "Restoration LSP is precomputed prior to the failure."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity restoration-scheme-presignaled { + base restoration-scheme-type; + description + "Restoration LSP is presignaled prior to the failure."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity lsp-protection-type { + description + "Base identity from which LSP protection types are derived."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity lsp-protection-unprotected { + base lsp-protection-type; + description + "'Unprotected' LSP protection type."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity lsp-protection-reroute-extra { + base lsp-protection-type; + status obsolete; + description + "'(Full) Rerouting' LSP protection type. + + This identity has been obsoleted: the + 'restoration-scheme-rerouting' identity SHOULD be used + instead."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity lsp-protection-reroute { + base lsp-protection-type; + status obsolete; + description + "'Rerouting without Extra-Traffic' LSP protection type. + + This identity has been obsoleted: the + 'restoration-scheme-rerouting' identity SHOULD be used + instead."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity lsp-protection-1-for-n { + base lsp-protection-type; + description + "'1:N Protection with Extra-Traffic' LSP protection type."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity lsp-protection-1-for-1 { + base lsp-protection-type; + description + "LSP protection '1:1 Protection Type'."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity lsp-protection-unidir-1-plus-1 { + base lsp-protection-type; + description + "'1+1 Unidirectional Protection' LSP protection type."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity lsp-protection-bidir-1-plus-1 { + base lsp-protection-type; + description + "'1+1 Bidirectional Protection' LSP protection type."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity lsp-protection-extra-traffic { + base lsp-protection-type; + description + "Extra-Traffic LSP protection type."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery"; + } + + identity lsp-protection-state { + description + "Base identity of protection states for reporting purposes."; + } + + identity normal { + base lsp-protection-state; + description + "Normal state."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity signal-fail-of-protection { + base lsp-protection-state; + description + "The protection transport entity has a signal fail condition + that is of higher priority than the forced switchover + command."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity lockout-of-protection { + base lsp-protection-state; + description + "A Loss of Protection (LoP) command is active."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity forced-switch { + base lsp-protection-state; + description + "A forced switchover command is active."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity signal-fail { + base lsp-protection-state; + description + "There is a signal fail condition on either the working path + or the protection path."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity signal-degrade { + base lsp-protection-state; + description + "There is a signal degrade condition on either the working + path or the protection path."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity manual-switch { + base lsp-protection-state; + description + "A manual switchover command is active."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity wait-to-restore { + base lsp-protection-state; + description + "A Wait-to-Restore (WTR) timer is running."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity do-not-revert { + base lsp-protection-state; + description + "A Do Not Revert (DNR) condition is active because of + non-revertive behavior."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity failure-of-protocol { + base lsp-protection-state; + description + "LSP protection is not working because of a protocol failure + condition."; + reference + "RFC 7271: MPLS Transport Profile (MPLS-TP) Linear Protection + to Match the Operational Expectations of + Synchronous Digital Hierarchy, Optical Transport + Network, and Ethernet Transport Network Operators + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity protection-external-commands { + description + "Base identity from which protection-related external commands + used for troubleshooting purposes are derived."; + } + + identity action-freeze { + base protection-external-commands; + description + "A temporary configuration action initiated by an operator + command that prevents any switchover action from being taken + and, as such, freezes the current state."; + reference + "RFC 7271: MPLS Transport Profile (MPLS-TP) Linear Protection + to Match the Operational Expectations of + Synchronous Digital Hierarchy, Optical Transport + Network, and Ethernet Transport Network Operators + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity clear-freeze { + base protection-external-commands; + description + "An action that clears the active freeze state."; + reference + "RFC 7271: MPLS Transport Profile (MPLS-TP) Linear Protection + to Match the Operational Expectations of + Synchronous Digital Hierarchy, Optical Transport + Network, and Ethernet Transport Network Operators + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity action-lockout-of-normal { + base protection-external-commands; + description + "A temporary configuration action initiated by an operator + command to ensure that the normal traffic is not allowed + to use the protection transport entity."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity clear-lockout-of-normal { + base protection-external-commands; + description + "An action that clears the active lockout of the + normal state."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity action-lockout-of-protection { + base protection-external-commands; + description + "A temporary configuration action initiated by an operator + command to ensure that the protection transport entity is + temporarily not available to transport a traffic signal + (either normal or Extra-Traffic)."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity action-forced-switch { + base protection-external-commands; + description + "A switchover action initiated by an operator command to + switch the Extra-Traffic signal, the normal traffic signal, + or the null signal to the protection transport entity, + unless a switchover command of equal or higher priority is + in effect."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity action-manual-switch { + base protection-external-commands; + description + "A switchover action initiated by an operator command to + switch the Extra-Traffic signal, the normal traffic signal, + or the null signal to the protection transport entity, + unless a fault condition exists on other transport entities + or a switchover command of equal or higher priority is in + effect."; + reference + "RFC 4872: RSVP-TE Extensions in Support of End-to-End + Generalized Multi-Protocol Label Switching (GMPLS) + Recovery + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity action-exercise { + base protection-external-commands; + description + "An action that starts testing whether or not Automatic + Protection Switching (APS) communication is operating + correctly. It is of lower priority than any + other state or command."; + reference + "RFC 7271: MPLS Transport Profile (MPLS-TP) Linear Protection + to Match the Operational Expectations of + Synchronous Digital Hierarchy, Optical Transport + Network, and Ethernet Transport Network Operators + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity clear { + base protection-external-commands; + description + "An action that clears the active near-end lockout of a + protection, forced switchover, manual switchover, + Wait-to-Restore (WTR) state, or exercise command."; + reference + "RFC 6378: MPLS Transport Profile (MPLS-TP) Linear Protection + RFC 4427: Recovery (Protection and Restoration) Terminology + for Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + + identity switching-capabilities { + description + "Base identity for interface switching capabilities."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity switching-psc1 { + base switching-capabilities; + description + "Packet-Switch Capable-1 (PSC-1)."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity switching-evpl { + base switching-capabilities; + description + "Ethernet Virtual Private Line (EVPL)."; + reference + "RFC 6004: Generalized MPLS (GMPLS) Support for Metro + Ethernet Forum and G.8011 Ethernet Service + Switching"; + } + + identity switching-l2sc { + base switching-capabilities; + description + "Layer-2 Switch Capable (L2SC)."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity switching-tdm { + base switching-capabilities; + description + "Time-Division-Multiplex Capable (TDM)."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity switching-otn { + base switching-capabilities; + description + "OTN-TDM capable."; + reference + "RFC 7138: Traffic Engineering Extensions to OSPF for GMPLS + Control of Evolving G.709 Optical Transport + Networks"; + } + + identity switching-dcsc { + base switching-capabilities; + description + "Data Channel Switching Capable (DCSC)."; + reference + "RFC 6002: Generalized MPLS (GMPLS) Data Channel + Switching Capable (DCSC) and Channel Set Label + Extensions"; + } + + identity switching-lsc { + base switching-capabilities; + description + "Lambda-Switch Capable (LSC)."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity switching-fsc { + base switching-capabilities; + description + "Fiber-Switch Capable (FSC)."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity lsp-encoding-types { + description + "Base identity for encoding types."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity lsp-encoding-packet { + base lsp-encoding-types; + description + "Packet LSP encoding."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity lsp-encoding-ethernet { + base lsp-encoding-types; + description + "Ethernet LSP encoding."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity lsp-encoding-pdh { + base lsp-encoding-types; + description + "ANSI/ETSI PDH LSP encoding."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity lsp-encoding-sdh { + base lsp-encoding-types; + description + "SDH ITU-T G.707 / SONET ANSI T1.105 LSP encoding."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity lsp-encoding-digital-wrapper { + base lsp-encoding-types; + description + "Digital Wrapper LSP encoding."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity lsp-encoding-lambda { + base lsp-encoding-types; + description + "Lambda (photonic) LSP encoding."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity lsp-encoding-fiber { + base lsp-encoding-types; + description + "Fiber LSP encoding."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity lsp-encoding-fiber-channel { + base lsp-encoding-types; + description + "FiberChannel LSP encoding."; + reference + "RFC 3471: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Functional Description"; + } + + identity lsp-encoding-oduk { + base lsp-encoding-types; + description + "G.709 ODUk (Digital Path) LSP encoding."; + reference + "RFC 4328: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Extensions for G.709 Optical Transport + Networks Control"; + } + + identity lsp-encoding-optical-channel { + base lsp-encoding-types; + description + "G.709 Optical Channel LSP encoding."; + reference + "RFC 4328: Generalized Multi-Protocol Label Switching (GMPLS) + Signaling Extensions for G.709 Optical Transport + Networks Control"; + } + + identity lsp-encoding-line { + base lsp-encoding-types; + description + "Line (e.g., 8B/10B) LSP encoding."; + reference + "RFC 6004: Generalized MPLS (GMPLS) Support for Metro + Ethernet Forum and G.8011 Ethernet Service + Switching"; + } + + identity path-signaling-type { + description + "Base identity from which specific LSP path setup types + are derived."; + } + + identity path-setup-static { + base path-signaling-type; + description + "Static LSP provisioning path setup."; + } + + identity path-setup-rsvp { + base path-signaling-type; + description + "RSVP-TE signaling path setup."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels"; + } + + identity path-setup-sr { + base path-signaling-type; + description + "Segment-routing path setup."; + } + + identity path-scope-type { + description + "Base identity from which specific path scope types are + derived."; + } + + identity path-scope-segment { + base path-scope-type; + description + "Path scope segment."; + reference + "RFC 4873: GMPLS Segment Recovery"; + } + + identity path-scope-end-to-end { + base path-scope-type; + description + "Path scope end to end."; + reference + "RFC 4873: GMPLS Segment Recovery"; + } + + identity route-usage-type { + description + "Base identity for route usage."; + } + + identity route-include-object { + base route-usage-type; + description + "'Include route' object."; + } + + identity route-exclude-object { + base route-usage-type; + description + "'Exclude route' object."; + reference + "RFC 4874: Exclude Routes - Extension to Resource ReserVation + Protocol-Traffic Engineering (RSVP-TE)"; + } + + identity route-exclude-srlg { + base route-usage-type; + description + "Excludes SRLGs."; + reference + "RFC 4874: Exclude Routes - Extension to Resource ReserVation + Protocol-Traffic Engineering (RSVP-TE)"; + } + + identity path-metric-optimization-type { + description + "Base identity used to define the path metric optimization + types."; + } + + identity link-path-metric-type { + description + "Base identity used to define the link and the path metric + types. + + The unit of the path metric value is interpreted in the + context of the path metric type and the derived identities + SHOULD describe the unit of the path metric types they + define."; + } + + identity link-metric-type { + base link-path-metric-type; + description + "Base identity for the link metric types."; + } + + identity link-metric-te { + base link-metric-type; + description + "Traffic Engineering (TE) Link Metric."; + reference + "RFC 3630: Traffic Engineering (TE) Extensions to OSPF + Version 2, Section 2.5.5 + RFC 5305: IS-IS Extensions for Traffic Engineering, + Section 3.7"; + } + + identity link-metric-igp { + base link-metric-type; + description + "Interior Gateway Protocol (IGP) Link Metric."; + reference + "RFC 3785: Use of Interior Gateway Protocol (IGP) Metric + as a second MPLS Traffic Engineering (TE) + Metric"; + } + + identity link-metric-delay-average { + base link-metric-type; + description + "Unidirectional Link Delay, measured in units of + microseconds."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric + Extensions, Section 4.1 + RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions, Section 4.1"; + } + + identity link-metric-delay-minimum { + base link-metric-type; + description + "Minimum unidirectional Link Delay, measured in units of + microseconds."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric + Extensions, Section 4.2 + RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions, Section 4.2"; + } + + identity link-metric-delay-maximum { + base link-metric-type; + description + "Maximum unidirectional Link Delay, measured in units of + microseconds."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric + Extensions, Section 4.2 + RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions, Section 4.2"; + } + + identity link-metric-residual-bandwidth { + base link-metric-type; + description + "Unidirectional Residual Bandwidth, measured in units of + bytes per second. + + It is defined to be Maximum Bandwidth minus the bandwidth + currently allocated to LSPs."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric + Extensions, Section 4.5 + RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions, Section 4.5"; + } + + identity path-metric-type { + base link-path-metric-type; + base path-metric-optimization-type; + description + "Base identity for the path metric types."; + } + + identity path-metric-te { + base path-metric-type; + description + "Traffic Engineering (TE) Path Metric."; + reference + "RFC 5440: Path Computation Element (PCE) Communication + Protocol (PCEP), Section 7.8"; + } + + identity path-metric-igp { + base path-metric-type; + description + "Interior Gateway Protocol (IGP) Path Metric."; + reference + "RFC 5440: Path Computation Element (PCE) Communication + Protocol (PCEP), section 7.8"; + } + + identity path-metric-hop { + base path-metric-type; + description + "Hop Count Path Metric."; + reference + "RFC 5440: Path Computation Element (PCE) Communication + Protocol (PCEP), Section 7.8"; + } + + identity path-metric-delay-average { + base path-metric-type; + description + "The Path Delay Metric, measured in units of + microseconds."; + reference + "RFC8233: Extensions to the Path Computation Element + Communication Protocol (PCEP) to Compute + Service-Aware Label Switched Paths (LSPs), + Section 3.1.1"; + } + + identity path-metric-delay-minimum { + base path-metric-type; + description + "The Path Min Delay Metric, measured in units of + microseconds."; + reference + "I-D.ietf-pce-sid-algo: Carrying SR-Algorithm information + in PCE-based Networks, + draft-ietf-pce-sid-algo-14, + Sections 3.5.1 and 3.5.2"; + } + + identity path-metric-residual-bandwidth { + base path-metric-type; + description + "The Path Residual Bandwidth, defined as the minimum Link + Residual Bandwidth all the links along the path. + + The Path Residual Bandwidth can be seen as the path + metric associated with the Maximum residual Bandwidth Path + (MBP) objective function."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity path-metric-optimize-includes { + base path-metric-optimization-type; + description + "A metric that optimizes the number of included resources + specified in a set."; + } + + identity path-metric-optimize-excludes { + base path-metric-optimization-type; + description + "A metric that optimizes to a maximum the number of excluded + resources specified in a set."; + } + + identity path-tiebreaker-type { + description + "Base identity for the path tiebreaker type."; + } + + identity path-tiebreaker-minfill { + base path-tiebreaker-type; + description + "Min-Fill LSP path placement: selects the path with the most + available bandwidth (load balance LSPs over more links)."; + } + + identity path-tiebreaker-maxfill { + base path-tiebreaker-type; + description + "Max-Fill LSP path placement: selects the path with the least + available bandwidth (packing more LSPs over few links)."; + } + + identity path-tiebreaker-random { + base path-tiebreaker-type; + description + "Random LSP path placement."; + } + + identity resource-affinities-type { + description + "Base identity for resource class affinities."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels + RFC 2702: Requirements for Traffic Engineering Over MPLS"; + } + + identity resource-aff-include-all { + base resource-affinities-type; + description + "The set of attribute filters associated with a + tunnel, all of which must be present for a link + to be acceptable."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels + RFC 2702: Requirements for Traffic Engineering Over MPLS"; + } + + identity resource-aff-include-any { + base resource-affinities-type; + description + "The set of attribute filters associated with a + tunnel, any of which must be present for a link + to be acceptable."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels + RFC 2702: Requirements for Traffic Engineering Over MPLS"; + } + + identity resource-aff-exclude-any { + base resource-affinities-type; + description + "The set of attribute filters associated with a + tunnel, any of which renders a link unacceptable."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels + RFC 2702: Requirements for Traffic Engineering Over MPLS"; + } + + identity te-optimization-criterion { + description + "Base identity for the TE optimization criteria."; + reference + "RFC 9522: Overview and Principles of Internet Traffic + Engineering"; + } + + identity not-optimized { + base te-optimization-criterion; + description + "Optimization is not applied."; + } + + identity cost { + base te-optimization-criterion; + description + "Optimized on cost."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity delay { + base te-optimization-criterion; + description + "Optimized on delay."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity path-computation-srlg-type { + description + "Base identity for SRLG path computation."; + } + + identity srlg-ignore { + base path-computation-srlg-type; + description + "Ignores SRLGs in the path computation."; + } + + identity srlg-strict { + base path-computation-srlg-type; + description + "Includes a strict SRLG check in the path computation."; + } + + identity srlg-preferred { + base path-computation-srlg-type; + description + "Includes a preferred SRLG check in the path computation."; + } + + identity srlg-weighted { + base path-computation-srlg-type; + description + "Includes a weighted SRLG check in the path computation."; + } + + identity path-computation-error-reason { + description + "Base identity for path computation error reasons."; + } + + identity path-computation-error-path-not-found { + base path-computation-error-reason; + description + "Path computation has failed because of an unspecified + reason."; + reference + "RFC 5440: Path Computation Element (PCE) Communication + Protocol (PCEP), Section 7.5"; + } + + identity path-computation-error-no-topology { + base path-computation-error-reason; + description + "Path computation has failed because there is no topology + with the provided topology-identifier."; + } + + identity path-computation-error-no-dependent-server { + base path-computation-error-reason; + description + "Path computation has failed because one or more dependent + path computation servers are unavailable. + + The dependent path computation server could be + a Backward-Recursive Path Computation (BRPC) downstream + PCE or a child PCE."; + reference + "RFC 5441: A Backward-Recursive PCE-Based Computation (BRPC) + Procedure to Compute Shortest Constrained + Inter-Domain Traffic Engineering Label Switched + Paths + RFC 8685: Path Computation Element Communication Protocol + (PCEP) Extensions for the Hierarchical Path + Computation Element (H-PCE) Architecture"; + } + + identity path-computation-error-pce-unavailable { + base path-computation-error-reason; + description + "Path computation has failed because PCE is not available. + + It corresponds to bit 31 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 5440: Path Computation Element (PCE) Communication + Protocol (PCEP) + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-no-inclusion-hop { + base path-computation-error-reason; + description + "Path computation has failed because there is no + node or link provided by one or more inclusion hops."; + } + + identity path-computation-error-destination-unknown-in-domain { + base path-computation-error-reason; + description + "Path computation has failed because the destination node is + unknown in indicated destination domain. + + It corresponds to bit 19 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 8685: Path Computation Element Communication Protocol + (PCEP) Extensions for the Hierarchical Path + Computation Element (H-PCE) Architecture + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-no-resource { + base path-computation-error-reason; + description + "Path computation has failed because there is no + available resource in one or more domains. + + It corresponds to bit 20 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 8685: Path Computation Element Communication Protocol + (PCEP) Extensions for the Hierarchical Path + Computation Element (H-PCE) Architecture + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-child-pce-unresponsive { + base path-computation-error-no-dependent-server; + description + "Path computation has failed because child PCE is not + responsive. + + It corresponds to bit 21 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 8685: Path Computation Element Communication Protocol + (PCEP) Extensions for the Hierarchical Path + Computation Element (H-PCE) Architecture + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-destination-domain-unknown { + base path-computation-error-reason; + description + "Path computation has failed because the destination domain + was unknown. + + It corresponds to bit 22 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 8685: Path Computation Element Communication Protocol + (PCEP) Extensions for the Hierarchical Path + Computation Element (H-PCE) Architecture + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-p2mp { + base path-computation-error-reason; + description + "Path computation has failed because of P2MP reachability + problem. + + It corresponds to bit 24 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 8306: Extensions to the Path Computation Element + Communication Protocol (PCEP) for + Point-to-Multipoint Traffic Engineering Label + Switched Paths + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-no-gco-migration { + base path-computation-error-reason; + description + "Path computation has failed because of no Global Concurrent + Optimization (GCO) migration path found. + + It corresponds to bit 26 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 5557: Path Computation Element Communication Protocol + (PCEP) Requirements and Protocol Extensions in + Support of Global Concurrent Optimization + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-no-gco-solution { + base path-computation-error-reason; + description + "Path computation has failed because of no GCO solution + found. + + It corresponds to bit 25 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 5557: Path Computation Element Communication Protocol + (PCEP) Requirements and Protocol Extensions in + Support of Global Concurrent Optimization + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-pks-expansion { + base path-computation-error-reason; + description + "Path computation has failed because of Path-Key Subobject + (PKS) expansion failure. + + It corresponds to bit 27 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 5520: Preserving Topology Confidentiality in + Inter-Domain Path Computation Using a + Path-Key-Based Mechanism + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-brpc-chain-unavailable { + base path-computation-error-no-dependent-server; + description + "Path computation has failed because PCE BRPC chain + unavailable. + + It corresponds to bit 28 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 5441: A Backward-Recursive PCE-Based Computation (BRPC) + Procedure to Compute Shortest Constrained + Inter-Domain Traffic Engineering Label Switched + Paths + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-source-unknown { + base path-computation-error-reason; + description + "Path computation has failed because source node is + unknown. + + It corresponds to bit 29 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 5440: Path Computation Element (PCE) Communication + Protocol (PCEP); + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-destination-unknown { + base path-computation-error-reason; + description + "Path computation has failed because destination node is + unknown. + + It corresponds to bit 30 of the Flags field of the + NO-PATH-VECTOR TLV."; + reference + "RFC 5440: Path Computation Element (PCE) Communication + Protocol (PCEP); + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity path-computation-error-no-server { + base path-computation-error-reason; + description + "Path computation has failed because path computation + server is unavailable."; + reference + "RFC 5440: Path Computation Element (PCE) Communication + Protocol (PCEP); + + https://www.iana.org/assignments/pcep + /pcep.xhtml#no-path-vector-tlv"; + } + + identity protocol-origin-type { + description + "Base identity for protocol origin type."; + } + + identity protocol-origin-api { + base protocol-origin-type; + description + "Protocol origin is via Application Programming Interface + (API)."; + } + + identity protocol-origin-pcep { + base protocol-origin-type; + description + "Protocol origin is Path Computation Engine Protocol + (PCEP)."; + reference + "RFC 5440: Path Computation Element (PCE) Communication + Protocol (PCEP)"; + } + + identity protocol-origin-bgp { + base protocol-origin-type; + description + "Protocol origin is Border Gateway Protocol (BGP)."; + reference + "RFC 9012: The BGP Tunnel Encapsulation Attribute"; + } + + identity svec-objective-function-type { + description + "Base identity for SVEC objective function type."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol (PCEP)"; + } + + identity svec-of-minimize-agg-bandwidth-consumption { + base svec-objective-function-type; + description + "Objective function for minimizing aggregate bandwidth + consumption (MBC)."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity svec-of-minimize-load-most-loaded-link { + base svec-objective-function-type; + description + "Objective function for minimizing the load on the link that + is carrying the highest load (MLL)."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity svec-of-minimize-cost-path-set { + base svec-objective-function-type; + description + "Objective function for minimizing the cost on a path set + (MCC)."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity svec-of-minimize-common-transit-domain { + base svec-objective-function-type; + description + "Objective function for minimizing the number of common + transit domains (MCTD)."; + reference + "RFC 8685: Path Computation Element Communication Protocol + (PCEP) Extensions for the Hierarchical Path + Computation Element (H-PCE) Architecture."; + } + + identity svec-of-minimize-shared-link { + base svec-objective-function-type; + description + "Objective function for minimizing the number of shared + links (MSL)."; + reference + "RFC 8685: Path Computation Element Communication Protocol + (PCEP) Extensions for the Hierarchical Path + Computation Element (H-PCE) Architecture."; + } + + identity svec-of-minimize-shared-srlg { + base svec-objective-function-type; + description + "Objective function for minimizing the number of shared + Shared Risk Link Groups (SRLG) (MSS)."; + reference + "RFC 8685: Path Computation Element Communication Protocol + (PCEP) Extensions for the Hierarchical Path + Computation Element (H-PCE) Architecture."; + } + + identity svec-of-minimize-shared-nodes { + base svec-objective-function-type; + description + "Objective function for minimizing the number of shared + nodes (MSN)."; + reference + "RFC 8685: Path Computation Element Communication Protocol + (PCEP) Extensions for the Hierarchical Path + Computation Element (H-PCE) Architecture."; + } + + identity svec-metric-type { + description + "Base identity for SVEC metric type."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol (PCEP)"; + } + + identity svec-metric-cumulative-te { + base svec-metric-type; + description + "Cumulative TE cost."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity svec-metric-cumulative-igp { + base svec-metric-type; + description + "Cumulative IGP cost."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity svec-metric-cumulative-hop { + base svec-metric-type; + description + "Cumulative Hop path metric."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity svec-metric-aggregate-bandwidth-consumption { + base svec-metric-type; + description + "Aggregate bandwidth consumption."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + identity svec-metric-load-of-the-most-loaded-link { + base svec-metric-type; + description + "Load of the most loaded link."; + reference + "RFC 5541: Encoding of Objective Functions in the Path + Computation Element Communication Protocol + (PCEP)"; + } + + /** + * TE bandwidth groupings + **/ + + grouping te-bandwidth { + description + "This grouping defines the generic TE bandwidth. + For some known data-plane technologies, specific modeling + structures are specified. The string-encoded 'te-bandwidth' + type is used for unspecified technologies. + The modeling structure can be augmented later for other + technologies."; + container te-bandwidth { + description + "Container that specifies TE bandwidth. The choices + can be augmented for specific data-plane technologies."; + choice technology { + default "generic"; + description + "Data-plane technology type."; + case generic { + leaf generic { + type te-bandwidth; + description + "Bandwidth specified in a generic format."; + } + } + } + } + } + + /** + * TE label groupings + **/ + + grouping te-label { + description + "This grouping defines the generic TE label. + The modeling structure can be augmented for each technology. + For unspecified technologies, 'rt-types:generalized-label' + is used."; + container te-label { + description + "Container that specifies the TE label. The choices can + be augmented for specific data-plane technologies."; + choice technology { + default "generic"; + description + "Data-plane technology type."; + case generic { + leaf generic { + type rt-types:generalized-label; + description + "TE label specified in a generic format."; + } + } + } + leaf direction { + type te-label-direction; + default "forward"; + description + "Label direction."; + } + } + } + + grouping te-topology-identifier { + description + "Augmentation for a TE topology."; + container te-topology-identifier { + description + "TE topology identifier container."; + leaf provider-id { + type te-global-id; + default "0"; + description + "An identifier to uniquely identify a provider. + If omitted, it assumes that the topology provider ID + value = 0 (the default)."; + } + leaf client-id { + type te-global-id; + default "0"; + description + "An identifier to uniquely identify a client. + If omitted, it assumes that the topology client ID + value = 0 (the default)."; + } + leaf topology-id { + type te-topology-id; + default ""; + description + "When the datastore contains several topologies, + 'topology-id' distinguishes between them. If omitted, + the default (empty) string for this leaf is assumed."; + } + } + } + + /** + * TE performance metrics groupings + **/ + + grouping performance-metrics-one-way-delay-loss { + description + "Performance Metrics (PM) information in real time that can + be applicable to links or connections. PM defined in this + grouping are applicable to generic TE PM as well as packet TE + PM."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric Extensions + RFC 7823: Performance-Based Path Selection for Explicitly + Routed Label Switched Paths (LSPs) Using TE Metric + Extensions"; + leaf one-way-delay { + type uint32 { + range "0..16777215"; + } + description + "One-way delay or latency in microseconds."; + } + leaf one-way-delay-normality { + type te-types:performance-metrics-normality; + description + "One-way delay normality."; + } + } + + grouping performance-metrics-two-way-delay-loss { + description + "PM information in real time that can be applicable to links or + connections. PM defined in this grouping are applicable to + generic TE PM as well as packet TE PM."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric Extensions + RFC 7823: Performance-Based Path Selection for Explicitly + Routed Label Switched Paths (LSPs) Using TE Metric + Extensions"; + leaf two-way-delay { + type uint32 { + range "0..16777215"; + } + description + "Two-way delay or latency in microseconds."; + } + leaf two-way-delay-normality { + type te-types:performance-metrics-normality; + description + "Two-way delay normality."; + } + } + + grouping performance-metrics-one-way-bandwidth { + description + "PM information in real time that can be applicable to links. + PM defined in this grouping are applicable to generic TE PM + as well as packet TE PM."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric Extensions + RFC 7823: Performance-Based Path Selection for Explicitly + Routed Label Switched Paths (LSPs) Using TE Metric + Extensions"; + leaf one-way-residual-bandwidth { + type rt-types:bandwidth-ieee-float32; + units "bytes per second"; + default "0x0p0"; + description + "Residual bandwidth that subtracts tunnel reservations from + Maximum Bandwidth (or link capacity) (RFC 3630) and + provides an aggregated remainder across QoS classes."; + reference + "RFC 3630: Traffic Engineering (TE) Extensions to OSPF + Version 2"; + } + leaf one-way-residual-bandwidth-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "Residual bandwidth normality."; + } + leaf one-way-available-bandwidth { + type rt-types:bandwidth-ieee-float32; + units "bytes per second"; + default "0x0p0"; + description + "Available bandwidth that is defined to be residual + bandwidth minus the measured bandwidth used for the + actual forwarding of non-RSVP-TE LSP packets. For a + bundled link, available bandwidth is defined to be the + sum of the component link available bandwidths."; + } + leaf one-way-available-bandwidth-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "Available bandwidth normality."; + } + leaf one-way-utilized-bandwidth { + type rt-types:bandwidth-ieee-float32; + units "bytes per second"; + default "0x0p0"; + description + "Bandwidth utilization that represents the actual + utilization of the link (i.e., as measured in the router). + For a bundled link, bandwidth utilization is defined to + be the sum of the component link bandwidth utilizations."; + } + leaf one-way-utilized-bandwidth-normality { + type te-types:performance-metrics-normality; + default "normal"; + description + "Bandwidth utilization normality."; + } + } + + grouping one-way-performance-metrics { + description + "One-way PM throttle grouping."; + leaf one-way-delay { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "One-way delay or latency in microseconds."; + } + leaf one-way-residual-bandwidth { + type rt-types:bandwidth-ieee-float32; + units "bytes per second"; + default "0x0p0"; + description + "Residual bandwidth that subtracts tunnel reservations from + Maximum Bandwidth (or link capacity) (RFC 3630) and + provides an aggregated remainder across QoS classes."; + reference + "RFC 3630: Traffic Engineering (TE) Extensions to OSPF + Version 2"; + } + leaf one-way-available-bandwidth { + type rt-types:bandwidth-ieee-float32; + units "bytes per second"; + default "0x0p0"; + description + "Available bandwidth that is defined to be residual + bandwidth minus the measured bandwidth used for the + actual forwarding of non-RSVP-TE LSP packets. For a + bundled link, available bandwidth is defined to be the + sum of the component link available bandwidths."; + } + leaf one-way-utilized-bandwidth { + type rt-types:bandwidth-ieee-float32; + units "bytes per second"; + default "0x0p0"; + description + "Bandwidth utilization that represents the actual + utilization of the link (i.e., as measured in the router). + For a bundled link, bandwidth utilization is defined to + be the sum of the component link bandwidth utilizations."; + } + } + + grouping two-way-performance-metrics { + description + "Two-way PM throttle grouping."; + leaf two-way-delay { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "Two-way delay or latency in microseconds."; + } + } + + grouping performance-metrics-thresholds { + description + "Grouping for configurable thresholds for measured + attributes."; + uses one-way-performance-metrics; + uses two-way-performance-metrics; + } + + grouping performance-metrics-attributes { + description + "Contains PM attributes."; + container performance-metrics-one-way { + description + "One-way link performance information in real time."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric Extensions + RFC 7823: Performance-Based Path Selection for Explicitly + Routed Label Switched Paths (LSPs) Using TE Metric + Extensions"; + uses performance-metrics-one-way-delay-loss; + uses performance-metrics-one-way-bandwidth; + } + container performance-metrics-two-way { + description + "Two-way link performance information in real time."; + reference + "RFC 6374: Packet Loss and Delay Measurement for MPLS + Networks"; + uses performance-metrics-two-way-delay-loss; + } + } + + grouping performance-metrics-throttle-container { + description + "Controls PM throttling."; + container throttle { + must 'suppression-interval >= measure-interval' { + error-message "'suppression-interval' cannot be less than " + + "'measure-interval'."; + description + "Constraint on 'suppression-interval' and + 'measure-interval'."; + } + description + "Link performance information in real time."; + reference + "RFC 7471: OSPF Traffic Engineering (TE) Metric Extensions + RFC 8570: IS-IS Traffic Engineering (TE) Metric Extensions + RFC 7823: Performance-Based Path Selection for Explicitly + Routed Label Switched Paths (LSPs) Using TE Metric + Extensions"; + leaf one-way-delay-offset { + type uint32 { + range "0..16777215"; + } + default "0"; + description + "Offset value to be added to the measured delay value."; + } + leaf measure-interval { + type uint32; + default "30"; + description + "Interval, in seconds, to measure the extended metric + values."; + } + leaf advertisement-interval { + type uint32; + default "0"; + description + "Interval, in seconds, to advertise the extended metric + values."; + } + leaf suppression-interval { + type uint32 { + range "1..max"; + } + default "120"; + description + "Interval, in seconds, to suppress advertisement of the + extended metric values."; + reference + "RFC 8570: IS-IS Traffic Engineering (TE) Metric + Extensions, Section 6"; + } + container threshold-out { + uses performance-metrics-thresholds; + description + "If the measured parameter falls outside an upper bound + for all but the minimum-delay metric (or a lower bound + for the minimum-delay metric only) and the advertised + value is not already outside that bound, an 'anomalous' + announcement (anomalous bit set) will be triggered."; + } + container threshold-in { + uses performance-metrics-thresholds; + description + "If the measured parameter falls inside an upper bound + for all but the minimum-delay metric (or a lower bound + for the minimum-delay metric only) and the advertised + value is not already inside that bound, a 'normal' + announcement (anomalous bit cleared) will be triggered."; + } + container threshold-accelerated-advertisement { + description + "When the difference between the last advertised value and + the current measured value exceeds this threshold, an + 'anomalous' announcement (anomalous bit set) will be + triggered."; + uses performance-metrics-thresholds; + } + } + } + + /** + * TE tunnel generic groupings + **/ + + grouping explicit-route-hop { + description + "The explicit route entry grouping."; + choice type { + description + "The explicit route entry type."; + case numbered-node-hop { + container numbered-node-hop { + must "node-id-uri or node-id" { + description + "At least one node identifier MUST be present."; + } + leaf node-id-uri { + type nw:node-id; + description + "The identifier of a node in the topology."; + } + leaf node-id { + type te-node-id; + description + "The identifier of a node in the TE topology."; + } + leaf hop-type { + type te-hop-type; + default "strict"; + description + "Strict or loose hop."; + } + description + "Numbered node route hop."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels, + Section 4.3, EXPLICIT_ROUTE in RSVP-TE + RFC 3477: Signalling Unnumbered Links in Resource + ReSerVation Protocol - Traffic Engineering + (RSVP-TE)"; + } + } + case numbered-link-hop { + container numbered-link-hop { + leaf link-tp-id { + type te-tp-id; + mandatory true; + description + "TE Link Termination Point (LTP) identifier."; + } + leaf hop-type { + type te-hop-type; + default "strict"; + description + "Strict or loose hop."; + } + leaf direction { + type te-link-direction; + default "outgoing"; + description + "Link route object direction."; + } + description + "Numbered link explicit route hop."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels, + Section 4.3, EXPLICIT_ROUTE in RSVP-TE + RFC 3477: Signalling Unnumbered Links in Resource + ReSerVation Protocol - Traffic Engineering + (RSVP-TE)"; + } + } + case unnumbered-link-hop { + container unnumbered-link-hop { + must "(link-tp-id-uri or link-tp-id) and " + + "(node-id-uri or node-id)" { + description + "At least one node identifier and at least one Link + Termination Point (LTP) identifier MUST be present."; + } + leaf link-tp-id-uri { + type nt:tp-id; + description + "Link Termination Point (LTP) identifier."; + } + leaf link-tp-id { + type te-tp-id; + description + "TE LTP identifier. The combination of the TE link ID + and the TE node ID is used to identify an unnumbered + TE link."; + } + leaf node-id-uri { + type nw:node-id; + description + "The identifier of a node in the topology."; + } + leaf node-id { + type te-node-id; + description + "The identifier of a node in the TE topology."; + } + leaf hop-type { + type te-hop-type; + default "strict"; + description + "Strict or loose hop."; + } + leaf direction { + type te-link-direction; + default "outgoing"; + description + "Link route object direction."; + } + description + "Unnumbered link explicit route hop."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels, + Section 4.3, EXPLICIT_ROUTE in RSVP-TE + RFC 3477: Signalling Unnumbered Links in Resource + ReSerVation Protocol - Traffic Engineering + (RSVP-TE)"; + } + } + case as-number { + container as-number-hop { + leaf as-number { + type inet:as-number; + mandatory true; + description + "The Autonomous System (AS) number."; + } + leaf hop-type { + type te-hop-type; + default "strict"; + description + "Strict or loose hop."; + } + description + "AS explicit route hop."; + } + } + case label { + container label-hop { + description + "Label hop type."; + uses te-label; + } + description + "The label explicit route hop type."; + } + } + } + + grouping record-route-state { + description + "The Record Route grouping."; + leaf index { + type uint32; + description + "Record Route hop index. The index is used to + identify an entry in the list. The order of entries + is defined by the user without relying on key values."; + } + choice type { + description + "The Record Route entry type."; + case numbered-node-hop { + container numbered-node-hop { + must "node-id-uri or node-id" { + description + "At least one node identifier MUST be present."; + } + description + "Numbered node route hop container."; + leaf node-id-uri { + type nw:node-id; + description + "The identifier of a node in the topology."; + } + leaf node-id { + type te-node-id; + description + "The identifier of a node in the TE topology."; + } + leaf-list flags { + type path-attribute-flags; + description + "Path attributes flags."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels + RFC 4090: Fast Reroute Extensions to RSVP-TE for LSP + Tunnels + RFC 4561: Definition of a Record Route Object (RRO) + Node-Id Sub-Object"; + } + } + description + "Numbered node route hop."; + } + case numbered-link-hop { + container numbered-link-hop { + description + "Numbered link route hop container."; + leaf link-tp-id { + type te-tp-id; + mandatory true; + description + "Numbered TE LTP identifier."; + } + leaf-list flags { + type path-attribute-flags; + description + "Path attributes flags."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels + RFC 4090: Fast Reroute Extensions to RSVP-TE for LSP + Tunnels + RFC 4561: Definition of a Record Route Object (RRO) + Node-Id Sub-Object"; + } + } + description + "Numbered link route hop."; + } + case unnumbered-link-hop { + container unnumbered-link-hop { + must "(link-tp-id-uri or link-tp-id) and " + + "(node-id-uri or node-id)" { + description + "At least one node identifier and at least one Link + Termination Point (LTP) identifier MUST be present."; + } + leaf link-tp-id-uri { + type nt:tp-id; + description + "Link Termination Point (LTP) identifier."; + } + leaf link-tp-id { + type te-tp-id; + description + "TE LTP identifier. The combination of the TE link ID + and the TE node ID is used to identify an unnumbered + TE link."; + } + leaf node-id-uri { + type nw:node-id; + description + "The identifier of a node in the topology."; + } + leaf node-id { + type te-node-id; + description + "The identifier of a node in the TE topology."; + } + leaf-list flags { + type path-attribute-flags; + description + "Path attributes flags."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels + RFC 4090: Fast Reroute Extensions to RSVP-TE for LSP + Tunnels + RFC 4561: Definition of a Record Route Object (RRO) + Node-Id Sub-Object"; + } + description + "Unnumbered link Record Route hop."; + reference + "RFC 3477: Signalling Unnumbered Links in Resource + ReSerVation Protocol - Traffic Engineering + (RSVP-TE)"; + } + description + "Unnumbered link route hop."; + } + case label { + container label-hop { + description + "Label route hop type."; + uses te-label; + leaf-list flags { + type path-attribute-flags; + description + "Path attributes flags."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels + RFC 4090: Fast Reroute Extensions to RSVP-TE for LSP + Tunnels + RFC 4561: Definition of a Record Route Object (RRO) + Node-Id Sub-Object"; + } + } + description + "The label Record Route entry types."; + } + } + } + + grouping label-restriction-info { + description + "Label set item information."; + leaf restriction { + type enumeration { + enum inclusive { + description + "The label or label range is inclusive."; + } + enum exclusive { + description + "The label or label range is exclusive."; + } + } + default "inclusive"; + description + "Indicates whether the list item is inclusive or exclusive."; + } + leaf index { + type uint32; + description + "The index of the label restriction list entry."; + } + container label-start { + must "(not(../label-end/te-label/direction) and" + + " not(te-label/direction))" + + " or " + + "(../label-end/te-label/direction = te-label/direction)" + + " or " + + "(not(te-label/direction) and" + + " (../label-end/te-label/direction = 'forward'))" + + " or " + + "(not(../label-end/te-label/direction) and" + + " (te-label/direction = 'forward'))" { + error-message "'label-start' and 'label-end' must have the " + + "same direction."; + } + description + "This is the starting label if a label range is specified. + This is the label value if a single label is specified, + in which case the 'label-end' attribute is not set."; + uses te-label; + } + container label-end { + must "(not(../label-start/te-label/direction) and" + + " not(te-label/direction))" + + " or " + + "(../label-start/te-label/direction = te-label/direction)" + + " or " + + "(not(te-label/direction) and" + + " (../label-start/te-label/direction = 'forward'))" + + " or " + + "(not(../label-start/te-label/direction) and" + + " (te-label/direction = 'forward'))" { + error-message "'label-start' and 'label-end' must have the " + + "same direction."; + } + description + "This is the ending label if a label range is specified. + This attribute is not set if a single label is specified."; + uses te-label; + } + container label-step { + description + "The step increment between labels in the label range. + The label start/end values will have to be consistent + with the sign of label step. For example, + 'label-start' < 'label-end' enforces 'label-step' > 0 + 'label-start' > 'label-end' enforces 'label-step' < 0."; + choice technology { + default "generic"; + description + "Data-plane technology type."; + case generic { + leaf generic { + type int32; + default "1"; + description + "Label range step."; + } + } + } + } + leaf range-bitmap { + type yang:hex-string; + description + "When there are gaps between 'label-start' and 'label-end', + this attribute is used to specify the positions + of the used labels. This is represented in big endian as + 'hex-string'. + + In case the restriction is 'inclusive', the bit-position is + set if the corresponding mapped label is available. + In this case, if the range-bitmap is not present, all the + labels in the range are available. + + In case the restriction is 'exclusive', the bit-position is + set if the corresponding mapped label is not available. + In this case, if the range-bitmap is not present, all the + labels in the range are not available. + + The most significant byte in the hex-string is the farthest + to the left in the byte sequence. Leading zero bytes in the + configured value may be omitted for brevity. + Each bit position in the 'range-bitmap' 'hex-string' maps + to a label in the range derived from 'label-start'. + + For example, assuming that 'label-start' = 16000 and + 'range-bitmap' = 0x01000001, then: + + - bit position (0) is set, and the corresponding mapped + label from the range is 16000 + (0 * 'label-step') or + 16000 for default 'label-step' = 1. + - bit position (24) is set, and the corresponding mapped + label from the range is 16000 + (24 * 'label-step') or + 16024 for default 'label-step' = 1."; + } + } + + grouping label-set-info { + description + "Grouping for the list of label restrictions specifying what + labels may or may not be used."; + container label-restrictions { + description + "The label restrictions container."; + list label-restriction { + key "index"; + description + "The absence of the label restrictions container implies + that all labels are acceptable; otherwise, only restricted + labels are available."; + reference + "RFC 7579: General Network Element Constraint Encoding + for GMPLS-Controlled Networks"; + uses label-restriction-info; + } + } + } + + grouping optimization-metric-entry { + description + "Optimization metrics configuration grouping."; + leaf metric-type { + type identityref { + base path-metric-optimization-type; + } + description + "Identifies the 'metric-type' that the path computation + process uses for optimization."; + } + leaf weight { + type uint8; + default "1"; + description + "TE path metric normalization weight."; + } + container explicit-route-exclude-objects { + when "../metric-type = " + + "'te-types:path-metric-optimize-excludes'"; + description + "Container for the 'exclude route' object list."; + uses path-route-exclude-objects; + } + container explicit-route-include-objects { + when "../metric-type = " + + "'te-types:path-metric-optimize-includes'"; + description + "Container for the 'include route' object list."; + uses path-route-include-objects; + } + } + + grouping common-constraints { + description + "Common constraints grouping that can be set on + a constraint set or directly on the tunnel."; + uses te-bandwidth { + description + "A requested bandwidth to use for path computation."; + } + leaf link-protection { + type identityref { + base link-protection-type; + } + default "te-types:link-protection-unprotected"; + description + "Link protection type required for the links included + in the computed path."; + reference + "RFC 4202: Routing Extensions in Support of + Generalized Multi-Protocol Label Switching + (GMPLS)"; + } + leaf setup-priority { + type uint8 { + range "0..7"; + } + default "7"; + description + "TE LSP requested setup priority."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels"; + } + leaf hold-priority { + type uint8 { + range "0..7"; + } + default "7"; + description + "TE LSP requested hold priority."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels"; + } + leaf signaling-type { + type identityref { + base path-signaling-type; + } + default "te-types:path-setup-rsvp"; + description + "TE tunnel path signaling type."; + } + } + + grouping tunnel-constraints { + description + "Tunnel constraints grouping that can be set on + a constraint set or directly on the tunnel."; + leaf network-id { + type nw:network-id; + description + "The network topology identifier."; + } + uses te-topology-identifier; + uses common-constraints; + } + + grouping path-constraints-route-objects { + description + "List of route entries to be included or excluded when + performing the path computation."; + container explicit-route-objects { + description + "Container for the explicit route object lists."; + list route-object-exclude-always { + key "index"; + ordered-by user; + description + "List of route objects to always exclude from the path + computation."; + leaf index { + type uint32; + description + "Explicit Route Object index. The index is used to + identify an entry in the list. The order of entries + is defined by the user without relying on key values."; + } + uses explicit-route-hop; + } + list route-object-include-exclude { + key "index"; + ordered-by user; + description + "List of route objects to include or exclude in the path + computation."; + leaf explicit-route-usage { + type identityref { + base route-usage-type; + } + default "te-types:route-include-object"; + description + "Indicates whether to include or exclude the + route object. The default is to include it."; + } + leaf index { + type uint32; + description + "Route object include-exclude index. The index is used + to identify an entry in the list. The order of entries + is defined by the user without relying on key values."; + } + uses explicit-route-hop { + augment "type" { + case srlg { + container srlg { + description + "SRLG container."; + leaf srlg { + type uint32; + description + "SRLG value."; + } + } + description + "An SRLG value to be included or excluded."; + } + description + "Augmentation for a generic explicit route for SRLG + exclusion."; + } + } + } + } + } + + grouping path-route-include-objects { + description + "List of route objects to be included when performing + the path computation."; + list route-object-include-object { + key "index"; + ordered-by user; + description + "List of Explicit Route Objects to be included in the + path computation."; + leaf index { + type uint32; + description + "Route object entry index. The index is used to + identify an entry in the list. The order of entries + is defined by the user without relying on key values."; + } + uses explicit-route-hop; + } + } + + grouping path-route-exclude-objects { + description + "List of route objects to be excluded when performing + the path computation."; + list route-object-exclude-object { + key "index"; + ordered-by user; + description + "List of Explicit Route Objects to be excluded in the + path computation."; + leaf index { + type uint32; + description + "Route object entry index. The index is used to + identify an entry in the list. The order of entries + is defined by the user without relying on key values."; + } + uses explicit-route-hop { + augment "type" { + case srlg { + container srlg { + description + "SRLG container."; + leaf srlg { + type uint32; + description + "SRLG value."; + } + } + description + "An SRLG value to be included or excluded."; + } + description + "Augmentation for a generic explicit route for SRLG + exclusion."; + } + } + } + } + + grouping generic-path-metric-bounds { + description + "TE path metric bounds grouping."; + container path-metric-bounds { + description + "Top-level container for the list of path metric bounds."; + list path-metric-bound { + key "metric-type"; + description + "List of path metric bounds, which can apply to link and + path metrics. + + TE paths which have at least one path metric which + exceeds the specified bounds MUST NOT be selected. + + TE paths that traverse TE links which have at least one + link metric which exceeds the specified bounds MUST NOT + be selected."; + leaf metric-type { + type identityref { + base link-path-metric-type; + } + description + "Identifies an entry in the list of 'metric-type' items + bound for the TE path."; + } + leaf upper-bound { + type uint64; + default "0"; + description + "Upper bound on the specified 'metric-type'. + + A zero indicates an unbounded upper limit for the + specificied 'metric-type'. + + The unit of is interpreted in the context of the + 'metric-type' identity."; + } + } + } + } + + grouping generic-path-optimization { + description + "TE generic path optimization grouping."; + container optimizations { + description + "The objective function container that includes + attributes to impose when computing a TE path."; + choice algorithm { + description + "Optimizations algorithm."; + case metric { + if-feature "path-optimization-metric"; + /* Optimize by metric */ + list optimization-metric { + key "metric-type"; + description + "TE path metric type."; + uses optimization-metric-entry; + } + /* Tiebreakers */ + container tiebreakers { + status deprecated; + description + "Container for the list of tiebreakers. + + This container has been deprecated by the tiebreaker + leaf."; + list tiebreaker { + key "tiebreaker-type"; + status deprecated; + description + "The list of tiebreaker criteria to apply on an + equally favored set of paths, in order to pick + the best."; + leaf tiebreaker-type { + type identityref { + base path-metric-type; + } + status deprecated; + description + "Identifies an entry in the list of tiebreakers."; + } + } + } + } + case objective-function { + if-feature "path-optimization-objective-function"; + /* Objective functions */ + container objective-function { + description + "The objective function container that includes + attributes to impose when computing a TE path."; + leaf objective-function-type { + type identityref { + base objective-function-type; + } + default "te-types:of-minimize-cost-path"; + description + "Objective function entry."; + } + } + } + } + } + leaf tiebreaker { + type identityref { + base path-tiebreaker-type; + } + default "te-types:path-tiebreaker-random"; + description + "The tiebreaker criteria to apply on an equally favored set + of paths, in order to pick the best."; + } + } + + grouping generic-path-affinities { + description + "Path affinities grouping."; + container path-affinities-values { + description + "Path affinities represented as values."; + list path-affinities-value { + key "usage"; + description + "List of named affinity constraints."; + leaf usage { + type identityref { + base resource-affinities-type; + } + description + "Identifies an entry in the list of value affinity + constraints."; + } + leaf value { + type admin-groups; + default ""; + description + "The affinity value. The default is empty."; + } + } + } + container path-affinity-names { + description + "Path affinities represented as names."; + list path-affinity-name { + key "usage"; + description + "List of named affinity constraints."; + leaf usage { + type identityref { + base resource-affinities-type; + } + description + "Identifies an entry in the list of named affinity + constraints."; + } + list affinity-name { + key "name"; + leaf name { + type string; + description + "Identifies a named affinity entry."; + } + description + "List of named affinities."; + } + } + } + } + + grouping generic-path-srlgs { + description + "Path SRLG grouping."; + container path-srlgs-lists { + description + "Path SRLG properties container."; + list path-srlgs-list { + key "usage"; + description + "List of SRLG values to be included or excluded."; + leaf usage { + type identityref { + base route-usage-type; + } + description + "Identifies an entry in a list of SRLGs to either + include or exclude."; + } + leaf-list values { + type srlg; + description + "List of SRLG values."; + } + } + } + container path-srlgs-names { + description + "Container for the list of named SRLGs."; + list path-srlgs-name { + key "usage"; + description + "List of named SRLGs to be included or excluded."; + leaf usage { + type identityref { + base route-usage-type; + } + description + "Identifies an entry in a list of named SRLGs to either + include or exclude."; + } + leaf-list names { + type string; + description + "List of named SRLGs."; + } + } + } + } + + grouping generic-path-disjointness { + description + "Path disjointness grouping."; + leaf disjointness { + type te-path-disjointness; + description + "The type of resource disjointness. + When configured for a primary path, the disjointness level + applies to all secondary LSPs. When configured for a + secondary path, the disjointness level overrides the level + configured for the primary path."; + } + } + + grouping common-path-constraints-attributes { + description + "Common path constraints configuration grouping."; + uses common-constraints; + uses generic-path-metric-bounds; + uses generic-path-affinities; + uses generic-path-srlgs; + } + + grouping generic-path-constraints { + description + "Global named path constraints configuration grouping."; + container path-constraints { + description + "TE named path constraints container."; + uses common-path-constraints-attributes; + uses generic-path-disjointness; + } + } + + grouping generic-path-properties { + description + "TE generic path properties grouping."; + container path-properties { + config false; + description + "The TE path properties."; + list path-metric { + key "metric-type"; + description + "TE path metric type."; + leaf metric-type { + type identityref { + base path-metric-type; + } + description + "TE path metric type."; + } + leaf accumulative-value { + type uint64; + description + "TE path metric accumulative value."; + } + } + uses generic-path-affinities; + uses generic-path-srlgs; + container path-route-objects { + description + "Container for the list of route objects either returned by + the computation engine or actually used by an LSP."; + list path-route-object { + key "index"; + ordered-by user; + description + "List of route objects either returned by the computation + engine or actually used by an LSP."; + leaf index { + type uint32; + description + "Route object entry index. The index is used to + identify an entry in the list. The order of entries + is defined by the user without relying on key + values."; + } + uses explicit-route-hop; + } + } + } + } + + grouping encoding-and-switching-type { + description + "Common grouping to define the LSP encoding and + switching types"; + leaf encoding { + type identityref { + base te-types:lsp-encoding-types; + } + description + "LSP encoding type."; + reference + "RFC 3945: Generalized Multi-Protocol Label Switching (GMPLS) + Architecture"; + } + leaf switching-type { + type identityref { + base te-types:switching-capabilities; + } + description + "LSP switching type."; + reference + "RFC 3945: Generalized Multi-Protocol Label Switching (GMPLS) + Architecture"; + } + } + + grouping te-generic-node-id { + description + "A reusable grouping for a TE generic node identifier."; + leaf id { + type union { + type te-node-id; + type inet:ip-address; + type nw:node-id; + } + description + "The identifier of the node. + + It can be represented as IP address or dotted quad address + or as an URI. + + The type data node disambiguates the union type."; + } + leaf type { + type enumeration { + enum ip { + description + "IP address representation of the node identifier."; + } + enum te-id { + description + "TE identifier of the node"; + } + enum node-id { + description + "URI representation of the node identifier."; + } + } + description + "Type of node identifier representation."; + } + } +} diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-vpn-common@2021-09-10.yang b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-vpn-common@2021-09-10.yang new file mode 100644 index 0000000000000000000000000000000000000000..075437d3a43dd7d81f98f1103fd2e34e2571005b --- /dev/null +++ b/src/nbi/service/rest_server/nbi_plugins/ietf_network_slice/yang/ietf-vpn-common@2021-09-10.yang @@ -0,0 +1,2205 @@ +module ietf-vpn-common { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-vpn-common"; + prefix vpn-common; + + import ietf-netconf-acm { + prefix nacm; + reference + "RFC 8341: Network Configuration Access Control Model"; + } + import ietf-routing-types { + prefix rt-types; + reference + "RFC 8294: Common YANG Data Types for the Routing Area"; + } + import ietf-yang-types { + prefix yang; + reference + "RFC 6991: Common YANG Data Types, Section 3"; + } + import ietf-packet-fields { + prefix packet-fields; + reference + "RFC 8519: YANG Data Model for Network Access + Control Lists (ACLs)"; + } + + organization + "IETF OPSAWG (Operations and Management Area Working Group)"; + contact + "WG Web: <https://datatracker.ietf.org/wg/opsawg/> + WG List: <mailto:opsawg@ietf.org> + + Editor: Mohamed Boucadair + <mailto:mohamed.boucadair@orange.com> + Author: Samier Barguil + <mailto:samier.barguilgiraldo.ext@telefonica.com> + Author: Oscar Gonzalez de Dios + <mailto:oscar.gonzalezdedios@telefonica.com> + Author: Qin Wu + <mailto:bill.wu@huawei.com>"; + description + "This YANG module defines a common module that is meant + to be reused by various VPN-related modules (e.g., + Layer 3 VPN Service Model (L3SM), Layer 2 VPN Service + Model (L2SM), Layer 3 VPN Network Model (L3NM), Layer 2 + VPN Network Model (L2NM)). + + Copyright (c) 2021 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC XXXX; see + the RFC itself for full legal notices."; + + revision 2021-09-10 { + description + "Initial revision."; + reference + "RFC XXXX: A Layer 2/3 VPN Common YANG Model"; + } + + /******** Collection of VPN-related Features ********/ + /* + * Features related to encapsulation schemes + */ + + feature dot1q { + description + "Indicates the support for the Dot1q encapsulation."; + reference + "IEEE Std 802.1Q: Bridges and Bridged Networks"; + } + + feature qinq { + description + "Indicates the support for the QinQ encapsulation."; + reference + "IEEE Std 802.1ad: Provider Bridges"; + } + + feature vxlan { + description + "Indicates the support for the Virtual eXtensible + Local Area Network (VXLAN) encapsulation."; + reference + "RFC 7348: Virtual eXtensible Local Area Network (VXLAN): + A Framework for Overlaying Virtualized Layer 2 + Networks over Layer 3 Networks"; + } + + feature qinany { + description + "Indicates the support for the QinAny encapsulation. + The outer VLAN tag is set to a specific value but + the inner VLAN tag is set to any."; + } + + feature lag-interface { + description + "Indicates the support for Link Aggregation Group (LAG) + between VPN network accesses."; + reference + "IEEE Std. 802.1AX: Link Aggregation"; + } + + /* + * Features related to multicast + */ + + feature multicast { + description + "Indicates multicast capabilities support in a VPN."; + reference + "RFC 6513: Multicast in MPLS/BGP IP VPNs"; + } + + feature igmp { + description + "Indicates support for Internet Group Management Protocol + (IGMP)."; + reference + "RFC 1112: Host Extensions for IP Multicasting + RFC 2236: Internet Group Management Protocol, Version 2 + RFC 3376: Internet Group Management Protocol, Version 3"; + } + + feature mld { + description + "Indicates support for Multicast Listener Discovery (MLD)."; + reference + "RFC 2710: Multicast Listener Discovery (MLD) for IPv6 + RFC 3810: Multicast Listener Discovery Version 2 (MLDv2) + for IPv6"; + } + + feature pim { + description + "Indicates support for Protocol Independent Multicast (PIM)."; + reference + "RFC 7761: Protocol Independent Multicast - Sparse Mode + (PIM-SM): Protocol Specification (Revised)"; + } + + /* + * Features related to address family types + */ + + feature ipv4 { + description + "Indicates IPv4 support in a VPN. That is, IPv4 traffic + can be carried in the VPN, IPv4 addresses/prefixes can + be assigned to a VPN network access, IPv4 routes can be + installed for the CE/PE link, etc."; + reference + "RFC 791: Internet Protocol"; + } + + feature ipv6 { + description + "Indicates IPv6 support in a VPN. That is, IPv6 traffic + can be carried in the VPN, IPv6 addresses/prefixes can + be assigned to a VPN network access, IPv6 routes can be + installed for the CE/PE link, etc."; + reference + "RFC 8200: Internet Protocol, Version 6 (IPv6)"; + } + + /* + * Features related to routing protocols + */ + + feature rtg-ospf { + description + "Indicates support for the OSPF as the Provider Edge (PE)/ + Customer Edge (CE) routing protocol."; + reference + "RFC 4577: OSPF as the Provider/Customer Edge Protocol + for BGP/MPLS IP Virtual Private Networks (VPNs) + RFC 6565: OSPFv3 as a Provider Edge to Customer Edge + (PE-CE) Routing Protocol"; + } + + feature rtg-ospf-sham-link { + description + "Indicates support for OSPF sham links."; + reference + "RFC 4577: OSPF as the Provider/Customer Edge Protocol + for BGP/MPLS IP Virtual Private Networks (VPNs), + Section 4.2.7 + RFC 6565: OSPFv3 as a Provider Edge to Customer Edge + (PE-CE) Routing Protocol, Section 5"; + } + + feature rtg-bgp { + description + "Indicates support for BGP as the PE/CE routing protocol."; + reference + "RFC 4271: A Border Gateway Protocol 4 (BGP-4)"; + } + feature rtg-rip { + description + "Indicates support for RIP as the PE/CE routing protocol."; + reference + "RFC 2453: RIP Version 2 + RFC 2080: RIPng for IPv6"; + } + + feature rtg-isis { + description + "Indicates support for IS-IS as the PE/CE routing protocol."; + reference + "ISO10589: Intermediate System to Intermediate System intra- + domain routeing information exchange protocol for + use in conjunction with the protocol for providing + the connectionless-mode network service + (ISO 8473)"; + } + + feature rtg-vrrp { + description + "Indicates support for the Virtual Router Redundancy + Protocol (VRRP) in CE/PE link."; + reference + "RFC 5798: Virtual Router Redundancy Protocol (VRRP) Version 3 + for IPv4 and IPv6"; + } + + feature bfd { + description + "Indicates support for Bidirectional Forwarding Detection (BFD) + between the CE and the PE."; + reference + "RFC 5880: Bidirectional Forwarding Detection (BFD)"; + } + + /* + * Features related to VPN service constraints + */ + + feature bearer-reference { + description + "A bearer refers to properties of the CE-PE attachment that + are below Layer 3. + This feature indicates support for the bearer reference access + constraint. That is, the reuse of a network connection that was + already ordered to the service provider apart from the IP VPN + site."; + } + + feature placement-diversity { + description + "Indicates support for placement diversity constraints in the + customer premises. An example of these constraints may be to + avoid connecting a site network access to the same Provider + Edge as a target site network access."; + } + + /* + * Features related to bandwidth and Quality of Service (QoS) + */ + + feature qos { + description + "Indicates support for Classes of Service (CoSes) in the VPN."; + } + + feature inbound-bw { + description + "Indicates support for the inbound bandwidth in a VPN. That is, + support for specifying the download bandwidth from the service + provider network to the VPN site. Note that the L3SM uses + 'input' to identify the same feature. That terminology should + be deprecated in favor of the one defined in this module."; + } + + feature outbound-bw { + description + "Indicates support for the outbound bandwidth in a VPN. That is, + support for specifying the upload bandwidth from the VPN site + to the service provider network. Note that the L3SM uses + 'output' to identify the same feature. That terminology should + be deprecated in favor of the one defined in this module."; + } + + /* + * Features related to security and resilience + */ + + feature encryption { + description + "Indicates support for encryption in the VPN."; + } + + feature fast-reroute { + description + "Indicates support for Fast Reroute (FRR) capabilities for + a VPN site."; + } + + /* + * Features related to advanced VPN options + */ + + feature external-connectivity { + description + "Indicates support for the VPN to provide external + connectivity (e.g., Internet, private or public cloud)."; + reference + "RFC 4364: BGP/MPLS IP Virtual Private Networks + (VPNs), Section 11"; + } + + feature extranet-vpn { + description + "Indicates support for extranet VPNs. That is, the capability of + a VPN to access a list of other VPNs."; + reference + "RFC 4364: BGP/MPLS IP Virtual Private Networks + (VPNs), Section 1.1"; + } + + feature carriers-carrier { + description + "Indicates support for Carrier-of-Carrier VPNs."; + reference + "RFC 4364: BGP/MPLS IP Virtual Private Networks + (VPNs), Section 9"; + } + + /* + * Address family related identities + */ + + identity address-family { + description + "Defines a type for the address family."; + } + + identity ipv4 { + base address-family; + description + "Identity for IPv4 address family."; + } + identity ipv6 { + base address-family; + description + "Identity for IPv6 address family."; + } + + identity dual-stack { + base address-family; + description + "Identity for IPv4 and IPv6 address family."; + } + + /* + * Identities related to VPN topology + */ + + identity vpn-topology { + description + "Base identity of the VPN topology."; + } + + identity any-to-any { + base vpn-topology; + description + "Identity for any-to-any VPN topology. All VPN sites + can communicate with each other without any restrictions."; + } + + identity hub-spoke { + base vpn-topology; + description + "Identity for Hub-and-Spoke VPN topology. All Spokes can + communicate only with Hubs but not with each other. Hubs + can communicate with each other."; + } + + identity hub-spoke-disjoint { + base vpn-topology; + description + "Identity for Hub-and-Spoke VPN topology where Hubs cannot + communicate with each other."; + } + + identity custom { + base vpn-topology; + description + "Identity for custom VPN topologies where the role of the nodes + is not strictly Hub or Spoke. The VPN topology is controlled by + the import/export policies. The custom topology reflects more + complex VPN nodes such as VPN node that acts as Hub for certain + nodes and Spoke to others."; + } + + /* + * Identities related to network access types + */ + + identity site-network-access-type { + description + "Base identity for site network access type."; + } + + identity point-to-point { + base site-network-access-type; + description + "Point-to-point access type."; + } + + identity multipoint { + base site-network-access-type; + description + "Multipoint access type."; + } + + identity irb { + base site-network-access-type; + description + "Integrated Routing Bridge (IRB). + Identity for pseudowire connections."; + } + + identity loopback { + base site-network-access-type; + description + "Loopback access type."; + } + + /* + * Identities related to operational and administrative status + */ + + identity operational-status { + description + "Base identity for the operational status."; + } + + identity op-up { + base operational-status; + description + "Operational status is Up/Enabled."; + } + + identity op-down { + base operational-status; + description + "Operational status is Down/Disabled."; + } + + identity op-unknown { + base operational-status; + description + "Operational status is Unknown."; + } + + identity administrative-status { + description + "Base identity for administrative status."; + } + + identity admin-up { + base administrative-status; + description + "Administrative status is Up/Enabled."; + } + + identity admin-down { + base administrative-status; + description + "Administrative status is Down/Disabled."; + } + + identity admin-testing { + base administrative-status; + description + "Administrative status is up for testing purposes."; + } + + identity admin-pre-deployment { + base administrative-status; + description + "Administrative status is pre-deployment phase. That is, + prior to the actual deployment of a service."; + } + + /* + * Identities related to site or node role + */ + + identity role { + description + "Base identity of a site or a node role."; + } + + identity any-to-any-role { + base role; + description + "Any-to-any role."; + } + + identity spoke-role { + base role; + description + "A node or a site is acting as a Spoke."; + } + + identity hub-role { + base role; + description + "A node or a site is acting as a Hub."; + } + + identity custom-role { + base role; + description + "VPN node with custom or complex role in the VPN. For some + sources/destinations it can behave as a Hub, but for others it + can act as a Spoke depending on the configured policy."; + } + + /* + * Identities related to VPN service constraints + */ + + identity placement-diversity { + description + "Base identity for access placement constraints."; + } + + identity bearer-diverse { + base placement-diversity; + description + "Bearer diversity. + The bearers should not use common elements."; + } + + identity pe-diverse { + base placement-diversity; + description + "PE diversity."; + } + + identity pop-diverse { + base placement-diversity; + description + "Point Of Presence (POP) diversity."; + } + + identity linecard-diverse { + base placement-diversity; + description + "Linecard diversity."; + } + + identity same-pe { + base placement-diversity; + description + "Having sites connected on the same PE."; + } + + identity same-bearer { + base placement-diversity; + description + "Having sites connected using the same bearer."; + } + + /* + * Identities related to service types + */ + + identity service-type { + description + "Base identity for service type."; + } + + identity l3vpn { + base service-type; + description + "L3VPN service."; + reference + "RFC 4364: BGP/MPLS IP Virtual Private Networks (VPNs)"; + } + + identity vpls { + base service-type; + description + "VPLS service."; + reference + "RFC 4761: Virtual Private LAN Service (VPLS) Using BGP for + Auto-Discovery and Signaling + RFC 4762: Virtual Private LAN Service (VPLS) Using Label + Distribution Protocol (LDP) Signaling"; + } + + identity vpws { + base service-type; + description + "Virtual Private Wire Service (VPWS) service."; + reference + "RFC 4664: Framework for Layer 2 Virtual Private Networks + (L2VPNs), Section 3.1.1"; + } + + identity vpws-evpn { + base service-type; + description + "EVPN used to support VPWS service."; + reference + "RFC 8214: Virtual Private Wire Service Support in Ethernet VPN"; + } + + identity pbb-evpn { + base service-type; + description + "Provider Backbone Bridging (PBB) EVPNs service."; + reference + "RFC 7623: Provider Backbone Bridging Combined with Ethernet VPN + (PBB-EVPN)"; + } + + identity mpls-evpn { + base service-type; + description + "MPLS-based EVPN service."; + reference + "RFC 7432: BGP MPLS-Based Ethernet VPN"; + } + + identity vxlan-evpn { + base service-type; + description + "VXLAN-based EVPN service."; + reference + "RFC 8365: A Network Virtualization Overlay Solution Using + Ethernet VPN (EVPN)"; + } + + /* + * Identities related to VPN signaling type + */ + + identity vpn-signaling-type { + description + "Base identity for VPN signaling types"; + } + + identity bgp-signaling { + base vpn-signaling-type; + description + "Layer 2 VPNs using BGP signaling."; + reference + "RFC 6624: Layer 2 Virtual Private Networks Using BGP for + Auto-Discovery and Signaling + RFC 7432: BGP MPLS-Based Ethernet VPN"; + } + + identity ldp-signaling { + base vpn-signaling-type; + description + "Targeted Label Distribution Protocol (LDP) signaling."; + reference + "RFC 5036: LDP Specification"; + } + + identity l2tp-signaling { + base vpn-signaling-type; + description + "Layer Two Tunneling Protocol (L2TP) signaling."; + reference + "RFC 3931: Layer Two Tunneling Protocol - Version 3 (L2TPv3)"; + } + + /* + * Identities related to routing protocols + */ + + identity routing-protocol-type { + description + "Base identity for routing protocol type."; + } + + identity static-routing { + base routing-protocol-type; + description + "Static routing protocol."; + } + + identity bgp-routing { + if-feature "rtg-bgp"; + base routing-protocol-type; + description + "BGP routing protocol."; + reference + "RFC 4271: A Border Gateway Protocol 4 (BGP-4)"; + } + + identity ospf-routing { + if-feature "rtg-ospf"; + base routing-protocol-type; + description + "OSPF routing protocol."; + reference + "RFC 4577: OSPF as the Provider/Customer Edge Protocol + for BGP/MPLS IP Virtual Private Networks(VPNs) + RFC 6565: OSPFv3 as a Provider Edge to Customer Edge + (PE-CE) Routing Protocol"; + } + + identity rip-routing { + if-feature "rtg-rip"; + base routing-protocol-type; + description + "RIP routing protocol."; + reference + "RFC 2453: RIP Version 2 + RFC 2080: RIPng for IPv6"; + } + + identity isis-routing { + if-feature "rtg-isis"; + base routing-protocol-type; + description + "IS-IS routing protocol."; + reference + "ISO10589: Intermediate System to Intermediate System intra- + domain routeing information exchange protocol for + use in conjunction with the protocol for providing + the connectionless-mode network service + (ISO 8473)"; + } + + identity vrrp-routing { + if-feature "rtg-vrrp"; + base routing-protocol-type; + description + "VRRP protocol. + + This is to be used when LANs are directly connected to PEs."; + reference + "RFC 5798: Virtual Router Redundancy Protocol (VRRP) Version 3 + for IPv4 and IPv6"; + } + + identity direct-routing { + base routing-protocol-type; + description + "Direct routing. + + This is to be used when LANs are directly connected to PEs + and must be advertised in the VPN."; + } + + identity any-routing { + base routing-protocol-type; + description + "Any routing protocol. + + This can be, e.g., used to set policies that apply to any + routing protocol in place."; + } + + identity isis-level { + if-feature "rtg-isis"; + description + "Base identity for the IS-IS level."; + reference + "ISO10589: Intermediate System to Intermediate System intra- + domain routeing information exchange protocol for + use in conjunction with the protocol for providing + the connectionless-mode network service + (ISO 8473)"; + } + + identity level-1 { + base isis-level; + description + "IS-IS level 1."; + } + + identity level-2 { + base isis-level; + description + "IS-IS level 2."; + } + + identity level-1-2 { + base isis-level; + description + "IS-IS levels 1 and 2."; + } + + identity bfd-session-type { + if-feature "bfd"; + description + "Base identity for the BFD session type."; + } + + identity classic-bfd { + base bfd-session-type; + description + "Classic BFD."; + reference + "RFC 5880: Bidirectional Forwarding Detection (BFD)"; + } + + identity s-bfd { + base bfd-session-type; + description + "Seamless BFD."; + reference + "RFC 7880: Seamless Bidirectional Forwarding Detection (S-BFD)"; + } + + /* + * Identities related to Routes Import and Export + */ + + identity ie-type { + description + "Base identity for 'import/export' routing profiles. + These profiles can be reused between VPN nodes."; + } + + identity import { + base ie-type; + description + "'Import' routing profile."; + reference + "RFC 4364: BGP/MPLS IP Virtual Private Networks + (VPNs), Section 4.3.1"; + } + + identity export { + base ie-type; + description + "'Export' routing profile."; + reference + "RFC 4364: BGP/MPLS IP Virtual Private Networks + (VPNs), Section 4.3.1"; + } + + identity import-export { + base ie-type; + description + "'Import/export' routing profile."; + } + + /* + * Identities related to bandwidth and QoS + */ + + identity bw-direction { + description + "Base identity for the bandwidth direction."; + } + + identity inbound-bw { + if-feature "inbound-bw"; + base bw-direction; + description + "Inbound bandwidth."; + } + + identity outbound-bw { + if-feature "outbound-bw"; + base bw-direction; + description + "Outbound bandwidth."; + } + identity bw-type { + description + "Base identity for the bandwidth type."; + } + + identity bw-per-cos { + if-feature "qos"; + base bw-type; + description + "The bandwidth is per-CoS."; + } + + identity bw-per-port { + base bw-type; + description + "The bandwidth is per-site network access."; + } + + identity bw-per-site { + base bw-type; + description + "The bandwidth is per-site. It is applicable to all the site + network accesses within a site."; + } + + identity bw-per-service { + base bw-type; + description + "The bandwidth is per-VPN service."; + } + + identity qos-profile-direction { + if-feature "qos"; + description + "Base identity for the QoS profile direction."; + } + + identity site-to-wan { + base qos-profile-direction; + description + "Customer site to provider's network direction. + This is typically the CE-to-PE direction."; + } + + identity wan-to-site { + base qos-profile-direction; + description + "Provider's network to customer site direction. + This is typically the PE-to-CE direction."; + } + + identity both { + base qos-profile-direction; + description + "Both WAN-to-Site and Site-to-WAN directions."; + } + + /* + * Identities related to underlay transport instances + */ + + identity transport-instance-type { + description + "Base identity for underlay transport instance type."; + } + + identity virtual-network { + base transport-instance-type; + description + "Virtual network."; + reference + "RFC 8453: Framework for Abstraction and Control of TE + Networks (ACTN)"; + } + + identity enhanced-vpn { + base transport-instance-type; + description + "Enhanced VPN (VPN+). VPN+ is an approach that is + based on existing VPN and Traffic Engineering (TE) + technologies but adds characteristics that specific + services require over and above classical VPNs."; + reference + "I-D.ietf-teas-enhanced-vpn: + A Framework for Enhanced Virtual Private Network + (VPN+) Services"; + } + + identity ietf-network-slice { + base transport-instance-type; + description + "IETF network slice. An IETF network slice + is a logical network topology connecting a number of + endpoints using a set of shared or dedicated network + resources that are used to satisfy specific service + objectives."; + reference + "I-D.ietf-teas-ietf-network-slices: + Framework for IETF Network Slices"; + } + + /* + * Identities related to protocol types. These types are typically + * used to identify the underlay transport. + */ + + identity protocol-type { + description + "Base identity for Protocol Type."; + } + + identity ip-in-ip { + base protocol-type; + description + "Transport is based on IP-in-IP."; + reference + "RFC 2003: IP Encapsulation within IP + RFC 2473: Generic Packet Tunneling in IPv6 Specification"; + } + + identity ip-in-ipv4 { + base ip-in-ip; + description + "Transport is based on IP over IPv4."; + reference + "RFC 2003: IP Encapsulation within IP"; + } + + identity ip-in-ipv6 { + base ip-in-ip; + description + "Transport is based on IP over IPv6."; + reference + "RFC 2473: Generic Packet Tunneling in IPv6 Specification"; + } + + identity gre { + base protocol-type; + description + "Transport is based on Generic Routing Encapsulation (GRE)."; + reference + "RFC 1701: Generic Routing Encapsulation (GRE) + RFC 1702: Generic Routing Encapsulation over IPv4 networks + RFC 7676: IPv6 Support for Generic Routing Encapsulation (GRE)"; + } + + identity gre-v4 { + base gre; + description + "Transport is based on GRE over IPv4."; + reference + "RFC 1702: Generic Routing Encapsulation over IPv4 networks"; + } + + identity gre-v6 { + base gre; + description + "Transport is based on GRE over IPv6."; + reference + "RFC 7676: IPv6 Support for Generic Routing Encapsulation (GRE)"; + } + + identity vxlan-trans { + base protocol-type; + description + "Transport is based on VXLAN."; + reference + "RFC 7348: Virtual eXtensible Local Area Network (VXLAN): + A Framework for Overlaying Virtualized Layer 2 + Networks over Layer 3 Networks"; + } + + identity geneve { + base protocol-type; + description + "Transport is based on Generic Network Virtualization + Encapsulation (GENEVE)."; + reference + "RFC 8926: Geneve: Generic Network Virtualization Encapsulation"; + } + + identity ldp { + base protocol-type; + description + "Transport is based on LDP."; + reference + "RFC 5036: LDP Specification"; + } + + identity mpls-in-udp { + base protocol-type; + description + "Transport is MPLS in UDP."; + reference + "RFC 7510: Encapsulating MPLS in UDP"; + } + + identity sr { + base protocol-type; + description + "Transport is based on Segment Routing (SR)."; + reference + "RFC 8660: Segment Routing with the MPLS Data Plane + RFC 8663: MPLS Segment Routing over IP + RFC 8754: IPv6 Segment Routing Header (SRH)"; + } + + identity sr-mpls { + base sr; + description + "Transport is based on SR with MPLS."; + reference + "RFC 8660: Segment Routing with the MPLS Data Plane"; + } + + identity srv6 { + base sr; + description + "Transport is based on SR over IPv6."; + reference + "RFC 8754: IPv6 Segment Routing Header (SRH)"; + } + + identity sr-mpls-over-ip { + base sr; + description + "Transport is based on SR over MPLS over IP."; + reference + "RFC 8663: MPLS Segment Routing over IP"; + } + + identity rsvp-te { + base protocol-type; + description + "Transport setup relies upon RSVP-TE."; + reference + "RFC 3209: RSVP-TE: Extensions to RSVP for LSP Tunnels"; + } + + identity bgp-lu { + base protocol-type; + description + "Transport setup relies upon BGP-LU."; + reference + "RFC 8277: Using BGP to Bind MPLS Labels to Address Prefixes"; + } + + identity unknown { + base protocol-type; + description + "Not known protocol type."; + } + + /* + * Identities related to encapsulations + */ + + identity encapsulation-type { + description + "Base identity for the encapsulation type."; + } + + identity priority-tagged { + base encapsulation-type; + description + "Priority-tagged interface."; + } + + identity dot1q { + if-feature "dot1q"; + base encapsulation-type; + description + "Dot1q encapsulation."; + } + + identity qinq { + if-feature "qinq"; + base encapsulation-type; + description + "QinQ encapsulation."; + } + + identity qinany { + if-feature "qinany"; + base encapsulation-type; + description + "QinAny encapsulation."; + } + identity vxlan { + if-feature "vxlan"; + base encapsulation-type; + description + "VxLAN encapsulation."; + } + + identity ethernet-type { + base encapsulation-type; + description + "Ethernet encapsulation type."; + } + + identity vlan-type { + base encapsulation-type; + description + "VLAN encapsulation type."; + } + + identity untagged-int { + base encapsulation-type; + description + "Untagged interface type."; + } + + identity tagged-int { + base encapsulation-type; + description + "Tagged interface type."; + } + + identity lag-int { + if-feature "lag-interface"; + base encapsulation-type; + description + "LAG interface type."; + } + + /* + * Identities related to VLAN Tag + */ + + identity tag-type { + description + "Base identity for the tag types."; + } + + identity c-vlan { + base tag-type; + description + "Indicates Customer VLAN (C-VLAN) tag, normally using + the 0x8100 Ethertype."; + } + + identity s-vlan { + base tag-type; + description + "Indicates Service VLAN (S-VLAN) tag."; + } + + identity s-c-vlan { + base tag-type; + description + "Uses both an S-VLAN tag and a C-VLAN tag."; + } + + /* + * Identities related to VXLAN + */ + + identity vxlan-peer-mode { + if-feature "vxlan"; + description + "Base identity for the VXLAN peer mode."; + } + + identity static-mode { + base vxlan-peer-mode; + description + "VXLAN access in the static mode."; + } + + identity bgp-mode { + base vxlan-peer-mode; + description + "VXLAN access by BGP EVPN learning."; + } + + /* + * Identities related to multicast + */ + + identity multicast-gp-address-mapping { + if-feature "multicast"; + description + "Base identity for multicast group mapping type."; + } + + identity static-mapping { + base multicast-gp-address-mapping; + description + "Static mapping, i.e., attach the interface to the + multicast group as a static member."; + } + + identity dynamic-mapping { + base multicast-gp-address-mapping; + description + "Dynamic mapping, i.e., an interface is added to the + multicast group as a result of snooping."; + } + + identity multicast-tree-type { + if-feature "multicast"; + description + "Base identity for multicast tree type."; + } + + identity ssm-tree-type { + base multicast-tree-type; + description + "Source-Specific Multicast (SSM) tree type."; + } + + identity asm-tree-type { + base multicast-tree-type; + description + "Any-Source Multicast (ASM) tree type."; + } + + identity bidir-tree-type { + base multicast-tree-type; + description + "Bidirectional tree type."; + } + + identity multicast-rp-discovery-type { + if-feature "multicast"; + description + "Base identity for Rendezvous Point (RP) discovery type."; + } + + identity auto-rp { + base multicast-rp-discovery-type; + description + "Auto-RP discovery type."; + } + + identity static-rp { + base multicast-rp-discovery-type; + description + "Static type."; + } + + identity bsr-rp { + base multicast-rp-discovery-type; + description + "Bootstrap Router (BSR) discovery type."; + } + + identity group-management-protocol { + if-feature "multicast"; + description + "Base identity for multicast group management protocol."; + } + + identity igmp-proto { + base group-management-protocol; + description + "IGMP."; + reference + "RFC 1112: Host Extensions for IP Multicasting + RFC 2236: Internet Group Management Protocol, Version 2 + RFC 3376: Internet Group Management Protocol, Version 3"; + } + + identity mld-proto { + base group-management-protocol; + description + "MLD."; + reference + "RFC 2710: Multicast Listener Discovery (MLD) for IPv6 + RFC 3810: Multicast Listener Discovery Version 2 (MLDv2) + for IPv6"; + } + + identity pim-proto { + if-feature "pim"; + base routing-protocol-type; + description + "PIM."; + reference + "RFC 7761: Protocol Independent Multicast - Sparse Mode + (PIM-SM): Protocol Specification (Revised)"; + } + + identity igmp-version { + if-feature "igmp"; + description + "Base identity for IGMP version."; + } + + identity igmpv1 { + base igmp-version; + description + "IGMPv1."; + reference + "RFC 1112: Host Extensions for IP Multicasting"; + } + + identity igmpv2 { + base igmp-version; + description + "IGMPv2."; + reference + "RFC 2236: Internet Group Management Protocol, Version 2"; + } + + identity igmpv3 { + base igmp-version; + description + "IGMPv3."; + reference + "RFC 3376: Internet Group Management Protocol, Version 3"; + } + + identity mld-version { + if-feature "mld"; + description + "Base identity for MLD version."; + } + + identity mldv1 { + base mld-version; + description + "MLDv1."; + reference + "RFC 2710: Multicast Listener Discovery (MLD) for IPv6"; + } + + identity mldv2 { + base mld-version; + description + "MLDv2."; + reference + "RFC 3810: Multicast Listener Discovery Version 2 (MLDv2) + for IPv6"; + } + + /* + * Identities related to traffic types + */ + + identity tf-type { + description + "Base identity for the traffic type."; + } + + identity multicast-traffic { + base tf-type; + description + "Multicast traffic."; + } + + identity broadcast-traffic { + base tf-type; + description + "Broadcast traffic."; + } + + identity unknown-unicast-traffic { + base tf-type; + description + "Unknown unicast traffic."; + } + + /* + * Identities related to customer applications + */ + + identity customer-application { + description + "Base identity for customer applications."; + } + + identity web { + base customer-application; + description + "Web applications (e.g., HTTP, HTTPS)."; + } + + identity mail { + base customer-application; + description + "Mail application."; + } + + identity file-transfer { + base customer-application; + description + "File transfer application (e.g., FTP, SFTP)."; + } + + identity database { + base customer-application; + description + "Database application."; + } + + identity social { + base customer-application; + description + "Social-network application."; + } + + identity games { + base customer-application; + description + "Gaming application."; + } + + identity p2p { + base customer-application; + description + "Peer-to-peer application."; + } + + identity network-management { + base customer-application; + description + "Management application (e.g., Telnet, syslog, + SNMP)."; + } + + identity voice { + base customer-application; + description + "Voice application."; + } + + identity video { + base customer-application; + description + "Video conference application."; + } + + identity embb { + base customer-application; + description + "Enhanced Mobile Broadband (eMBB) application. + Note that an eMBB application demands network performance with a + wide variety of characteristics, such as data rate, latency, + loss rate, reliability, and many other parameters."; + } + + identity urllc { + base customer-application; + description + "Ultra-Reliable and Low Latency Communications + (URLLC) application. Note that an URLLC application demands + network performance with a wide variety of characteristics, such + as latency, reliability, and many other parameters."; + } + + identity mmtc { + base customer-application; + description + "Massive Machine Type Communications (mMTC) application. + Note that an mMTC application demands network performance with + a wide variety of characteristics, such as data rate, latency, + loss rate, reliability, and many other parameters."; + } + + /* + * Identities related to service bundling + */ + + identity bundling-type { + description + "The base identity for the bundling type. It supports a subset or + all CE-VLANs associated with an L2VPN service."; + } + + identity multi-svc-bundling { + base bundling-type; + description + "Multi-service bundling, i.e., multiple C-VLAN IDs + can be associated with an L2VPN service at a site."; + } + + identity one2one-bundling { + base bundling-type; + description + "One-to-one service bundling, i.e., each L2VPN can + be associated with only one C-VLAN ID at a site."; + } + + identity all2one-bundling { + base bundling-type; + description + "All-to-one bundling, i.e., all C-VLAN IDs are mapped + to one L2VPN service."; + } + + /* + * Identities related to Ethernet Services + */ + + identity control-mode { + description + "Base Identity for the type of control mode on Layer 2 + Control Protocol (L2CP)."; + } + + identity peer { + base control-mode; + description + "'peer' mode, i.e., participate in the protocol towards the CE. + Peering is common for Link Aggregation Control Protocol (LACP) + and the Ethernet Local Management Interface (E-LMI) and, + occasionally, for Link Layer Discovery Protocol (LLDP). + For VPLSs and VPWSs, the subscriber can also request that the + peer service provider enables spanning tree."; + } + + identity tunnel { + base control-mode; + description + "'tunnel' mode, i.e., pass to the egress or destination site. For + Ethernet Private Lines (EPLs), the expectation is that L2CP + frames are tunnelled."; + } + identity discard { + base control-mode; + description + "'Discard' mode, i.e., discard the frame."; + } + + identity neg-mode { + description + "Base identity for the negotiation mode."; + } + + identity full-duplex { + base neg-mode; + description + "Full-duplex negotiation mode."; + } + + identity auto-neg { + base neg-mode; + description + "Auto-negotiation mode."; + } + + /******** Collection of VPN-related Types ********/ + + typedef vpn-id { + type string; + description + "Defines an identifier that is used with a VPN module. + This can be, for example, a service identifier, a node + identifier, etc."; + } + + /******* VPN-related reusable groupings *******/ + + grouping vpn-description { + description + "Provides common VPN information."; + leaf vpn-id { + type vpn-common:vpn-id; + description + "A VPN identifier that uniquely identifies a VPN. + This identifier has a local meaning, e.g., within + a service provider network."; + } + leaf vpn-name { + type string; + description + "Used to associate a name with the service + in order to facilitate the identification of + the service."; + } + leaf vpn-description { + type string; + description + "Textual description of a VPN."; + } + leaf customer-name { + type string; + description + "Name of the customer that actually uses the VPN."; + } + } + + grouping vpn-profile-cfg { + description + "Grouping for VPN Profile configuration."; + container valid-provider-identifiers { + description + "Container for valid provider profile identifiers."; + list external-connectivity-identifier { + if-feature "external-connectivity"; + key "id"; + description + "List for profile identifiers that uniquely identify profiles + governing how external connectivity is provided to a VPN. + A profile indicates the type of external connectivity + (Internet, cloud, etc.), the sites/nodes that are associated + with a connectivity profile, etc. A profile can also indicate + filtering rules and/or address translation rules. Such + features may involve PE, P, or dedicated nodes as a function + of the deployment."; + leaf id { + type string; + description + "Identification of an external connectivity profile. The + profile only has significance within the service provider's + administrative domain."; + } + } + list encryption-profile-identifier { + key "id"; + description + "List for encryption profile identifiers."; + leaf id { + type string; + description + "Identification of the encryption profile to be used. The + profile only has significance within the service provider's + administrative domain."; + } + } + list qos-profile-identifier { + key "id"; + description + "List for QoS Profile Identifiers."; + leaf id { + type string; + description + "Identification of the QoS profile to be used. The + profile only has significance within the service provider's + administrative domain."; + } + } + list bfd-profile-identifier { + key "id"; + description + "List for BFD profile identifiers."; + leaf id { + type string; + description + "Identification of the BFD profile to be used. The + profile only has significance within the service provider's + administrative domain."; + } + } + list forwarding-profile-identifier { + key "id"; + description + "List for forwarding profile identifiers."; + leaf id { + type string; + description + "Identification of the forwarding profile to be used. + The profile only has significance within the service + provider's administrative domain."; + } + } + list routing-profile-identifier { + key "id"; + description + "List for Routing Profile Identifiers."; + leaf id { + type string; + description + "Identification of the routing profile to be used by the + routing protocols within sites, vpn-network-accesses, or + vpn-nodes for refering VRF's import/export policies. + + The profile only has significance within the service + provider's administrative domain."; + } + } + nacm:default-deny-write; + } + } + + grouping oper-status-timestamp { + description + "This grouping defines some operational parameters for the + service."; + leaf status { + type identityref { + base operational-status; + } + config false; + description + "Operations status."; + } + leaf last-change { + type yang:date-and-time; + config false; + description + "Indicates the actual date and time of the service status + change."; + } + } + + grouping service-status { + description + "Service status grouping."; + container status { + description + "Service status."; + container admin-status { + description + "Administrative service status."; + leaf status { + type identityref { + base administrative-status; + } + description + "Administrative service status."; + } + leaf last-change { + type yang:date-and-time; + description + "Indicates the actual date and time of the service status + change."; + } + } + container oper-status { + description + "Operational service status."; + uses oper-status-timestamp; + } + } + } + + grouping underlay-transport { + description + "This grouping defines the type of underlay transport for the + VPN service or how that underlay is set. It can include an + identifier to an abstract transport instance to which the VPN + is grafted or indicate a technical implementation that is + expressed as an ordered list of protocols."; + choice type { + description + "A choice based on the type of underlay transport + constraints."; + case abstract { + description + "Indicates that the transport constraint is an abstract + concept."; + leaf transport-instance-id { + type string; + description + "An optional identifier of the abstract transport instance."; + } + leaf instance-type { + type identityref { + base transport-instance-type; + } + description + "Indicates a transport instance type. For example, it can + be a VPN+, an IETF network slice, a virtual network, etc."; + } + } + case protocol { + description + "Indicates a list of protocols."; + leaf-list protocol { + type identityref { + base protocol-type; + } + ordered-by user; + description + "A client ordered list of transport protocols."; + } + } + } + } + + grouping vpn-route-targets { + description + "A grouping that specifies Route Target (RT) import-export rules + used in a BGP-enabled VPN."; + reference + "RFC 4364: BGP/MPLS IP Virtual Private Networks (VPNs) + RFC 4664: Framework for Layer 2 Virtual Private Networks + (L2VPNs)"; + list vpn-target { + key "id"; + description + "Route targets. AND/OR operations may be defined + based on the RTs assigment."; + leaf id { + type uint8; + description + "Identifies each VPN Target."; + } + list route-targets { + key "route-target"; + description + "List of RTs."; + leaf route-target { + type rt-types:route-target; + description + "Conveys an RT value."; + } + } + leaf route-target-type { + type rt-types:route-target-type; + mandatory true; + description + "Import/export type of the RT."; + } + } + container vpn-policies { + description + "VPN service policies. It contains references to the + import and export policies to be associated with the + VPN service."; + leaf import-policy { + type string; + description + "Identifies the 'import' policy."; + } + leaf export-policy { + type string; + description + "Identifies the 'export' policy."; + } + } + } + + grouping route-distinguisher { + description + "Grouping for route distinguisher (RD)."; + choice rd-choice { + description + "Route distinguisher choice between several options + on providing the route distinguisher value."; + case directly-assigned { + description + "Explicitly assign an RD value."; + leaf rd { + type rt-types:route-distinguisher; + description + "Indicates an RD value that is explicitly + assigned."; + } + } + case directly-assigned-suffix { + description + "The value of the Assigned Number subfield of the RD. + The Administrator subfield of the RD will be + based on other configuration information such as + router-id or ASN."; + leaf rd-suffix { + type uint16; + description + "Indicates the value of the Assigned Number + subfield that is explicitly assigned."; + } + } + case auto-assigned { + description + "The RD is auto-assigned."; + container rd-auto { + description + "The RD is auto-assigned."; + choice auto-mode { + description + "Indicates the auto-assignment mode. RD can be + automatically assigned with or without + indicating a pool from which the RD should be + taken. + + For both cases, the server will auto-assign an RD + value 'auto-assigned-rd' and use that value + operationally."; + case from-pool { + leaf rd-pool-name { + type string; + description + "The auto-assignment will be made from the pool + identified by the rd-pool-name."; + } + } + case full-auto { + leaf auto { + type empty; + description + "Indicates an RD is fully auto-assigned."; + } + } + } + leaf auto-assigned-rd { + type rt-types:route-distinguisher; + config false; + description + "The value of the auto-assigned RD."; + } + } + } + case auto-assigned-suffix { + description + "The value of the Assigned Number subfield will + be auto-assigned. The Administrator subfield + will be based on other configuration information such as + router-id or ASN."; + container rd-auto-suffix { + description + "The Assigned Number subfield is auto-assigned."; + choice auto-mode { + description + "Indicates the auto-assignment mode of the Assigned Number + subfield. This number can be automatically assigned + with or without indicating a pool from which the value + should be taken. + + For both cases, the server will auto-assign + 'auto-assigned-rd-suffix' and use that value to build + the RD that will be used operationally."; + case from-pool { + leaf rd-pool-name { + type string; + description + "The assignment will be made from the pool identified + by the rd-pool-name."; + } + } + case full-auto { + leaf auto { + type empty; + description + "Indicates that the Assigned Number is fully auto + assigned."; + } + } + } + leaf auto-assigned-rd-suffix { + type uint16; + config false; + description + "Includes the value of the Assigned Number subfield that + is auto-assigned ."; + } + } + } + case no-rd { + description + "Use the empty type to indicate RD has no value and is not to + be auto-assigned."; + leaf no-rd { + type empty; + description + "No RD is assigned."; + } + } + } + } + + grouping vpn-components-group { + description + "Grouping definition to assign group-ids to associate VPN nodes, + sites, or network accesses."; + container groups { + description + "Lists the groups to which a VPN node, a site, or a network + access belongs to."; + list group { + key "group-id"; + description + "List of group-ids."; + leaf group-id { + type string; + description + "Is the group-id to which a VPN node, a site, or a network + access belongs to."; + } + } + } + } + + grouping placement-constraints { + description + "Constraints for placing a network access."; + list constraint { + key "constraint-type"; + description + "List of constraints."; + leaf constraint-type { + type identityref { + base placement-diversity; + } + description + "Diversity constraint type."; + } + container target { + description + "The constraint will apply against this list of groups."; + choice target-flavor { + description + "Choice for the group definition."; + case id { + list group { + key "group-id"; + description + "List of groups."; + leaf group-id { + type string; + description + "The constraint will apply against this particular + group-id."; + } + } + } + case all-accesses { + leaf all-other-accesses { + type empty; + description + "The constraint will apply against all other network + accesses of a site."; + } + } + case all-groups { + leaf all-other-groups { + type empty; + description + "The constraint will apply against all other groups that + the customer is managing."; + } + } + } + } + } + } + + grouping ports { + description + "Choice of specifying a source or destination port numbers."; + choice source-port { + description + "Choice of specifying the source port or referring to a group + of source port numbers."; + container source-port-range-or-operator { + description + "Source port definition."; + uses packet-fields:port-range-or-operator; + } + } + choice destination-port { + description + "Choice of specifying a destination port or referring to a group + of destination port numbers."; + container destination-port-range-or-operator { + description + "Destination port definition."; + uses packet-fields:port-range-or-operator; + } + } + } + + grouping qos-classification-policy { + description + "Configuration of the traffic classification policy."; + list rule { + key "id"; + ordered-by user; + description + "List of marking rules."; + leaf id { + type string; + description + "An identifier of the QoS classification policy rule."; + } + choice match-type { + default "match-flow"; + description + "Choice for classification."; + case match-flow { + choice l3 { + description + "Either IPv4 or IPv6."; + container ipv4 { + description + "Rule set that matches IPv4 header."; + uses packet-fields:acl-ip-header-fields; + uses packet-fields:acl-ipv4-header-fields; + } + container ipv6 { + description + "Rule set that matches IPv6 header."; + uses packet-fields:acl-ip-header-fields; + uses packet-fields:acl-ipv6-header-fields; + } + } + choice l4 { + description + "Includes Layer 4 specific information. + This version focuses on TCP and UDP."; + container tcp { + description + "Rule set that matches TCP header."; + uses packet-fields:acl-tcp-header-fields; + uses ports; + } + container udp { + description + "Rule set that matches UDP header."; + uses packet-fields:acl-udp-header-fields; + uses ports; + } + } + } + case match-application { + leaf match-application { + type identityref { + base customer-application; + } + description + "Defines the application to match."; + } + } + } + leaf target-class-id { + if-feature "qos"; + type string; + description + "Identification of the class of service. This identifier is + internal to the administration."; + } + } + } +} diff --git a/src/nbi/tests/data/agg-net-descriptor.json b/src/nbi/tests/data/agg-net-descriptor.json new file mode 100644 index 0000000000000000000000000000000000000000..bde4db62844084c0674312a60d7c1cbf8357de52 --- /dev/null +++ b/src/nbi/tests/data/agg-net-descriptor.json @@ -0,0 +1,628 @@ +{ + "contexts": [ + { + "context_id": { + "context_uuid": { + "uuid": "admin" + } + } + } + ], + "topologies": [ + { + "topology_id": { + "context_id": { + "context_uuid": { + "uuid": "admin" + } + }, + "topology_uuid": { + "uuid": "admin" + } + } + } + ], + "devices": [ + { + "device_id": { + "device_uuid": { + "uuid": "ip-net-controller" + } + }, + "name": "ip-net-controller", + "device_type": "ip-sdn-controller", + "device_operational_status": 1, + "device_drivers": [ + 13 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "10.10.10.10" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "80" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [], + "scheme": "http", + "username": "admin", + "password": "admin", + "base_url": "/restconf/v2/data", + "timeout": 120, + "verify": false + } + } + } + ] + }, + "device_endpoints": [] + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "name": "172.16.182.25", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "ip-net-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 0 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "200", + "name": "200", + "type": "optical", + "ce-ip": "128.32.33.2", + "address_ip": "128.32.33.254", + "address_prefix": "24", + "site_location": "access", + "mtu": "1500", + "ipv4_lan_prefixes": [ + { + "lan": "128.32.10.0/24", + "lan_tag": "10" + }, + { + "lan": "128.32.20.0/24", + "lan_tag": "20" + } + ] + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "name": "172.16.185.31", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "ip-net-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 0 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "name": "172.16.185.33", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "ip-net-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 0 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "name": "172.16.185.32", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "ip-net-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 0 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "200", + "name": "200", + "type": "optical", + "ce-ip": "172.10.33.2", + "address_ip": "172.10.33.254", + "address_prefix": "24", + "site_location": "cloud", + "mtu": "1500", + "ipv4_lan_prefixes": [ + { + "lan": "172.1.101.0/24", + "lan_tag": "101" + } + ] + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + } + ], + "links": [ + { + "link_id": { + "link_uuid": { + "uuid": "172.16.182.25-500" + } + }, + "name": "172.16.182.25-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.33-500" + } + }, + "name": "172.16.185.33-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.182.25-501" + } + }, + "name": "172.16.182.25-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.31-501" + } + }, + "name": "172.16.185.31-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.31-500" + } + }, + "name": "172.16.185.31-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.32-500" + } + }, + "name": "172.16.185.32-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.33-501" + } + }, + "name": "172.16.185.33-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.32-501" + } + }, + "name": "172.16.185.32-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/src/nbi/tests/data/camara-e2e-topology.json b/src/nbi/tests/data/camara-e2e-topology.json new file mode 100644 index 0000000000000000000000000000000000000000..0b314e104ef4ae31cfdb7be11f9e3912287cf5ae --- /dev/null +++ b/src/nbi/tests/data/camara-e2e-topology.json @@ -0,0 +1,1642 @@ +{ + "contexts": [ + { + "context_id": { + "context_uuid": { + "uuid": "admin" + } + } + } + ], + "topologies": [ + { + "topology_id": { + "context_id": { + "context_uuid": { + "uuid": "admin" + } + }, + "topology_uuid": { + "uuid": "admin" + } + } + } + ], + "devices": [ + { + "device_id": { + "device_uuid": { + "uuid": "agg-net-controller" + } + }, + "name": "agg-net-controller", + "device_type": "ietf-slice", + "device_operational_status": 1, + "device_drivers": [ + 14 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "10.0.58.9" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "80" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + } + ], + "scheme": "http", + "username": "admin", + "password": "admin", + "base_url": "/restconf/v2/data", + "timeout": 120, + "verify": false + } + } + } + ] + }, + "device_endpoints": [] + }, + { + "device_id": { + "device_uuid": { + "uuid": "nce-controller" + } + }, + "name": "nce-controller", + "device_type": "nce", + "device_operational_status": 1, + "device_drivers": [ + 15 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "10.10.10.10" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "80" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + } + ], + "scheme": "http", + "username": "admin", + "password": "admin", + "base_url": "/restconf/v2/data", + "timeout": 120, + "verify": false + } + } + } + ] + }, + "device_endpoints": [] + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "name": "172.16.182.25", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "agg-net-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 0, + 14 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "200", + "name": "200", + "type": "optical", + "address_ip": "128.32.33.254", + "address_prefix": "24", + "site_location": "access", + "mtu": "1500" + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "name": "172.16.185.31", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "agg-net-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 0, + 14 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "name": "172.16.185.33", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "agg-net-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 0, + 14 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "name": "172.16.185.32", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "agg-net-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 0, + 14 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "200", + "name": "200", + "type": "optical", + "ce-ip": "172.10.33.2", + "address_ip": "172.10.33.254", + "address_prefix": "24", + "site_location": "cloud", + "mtu": "1500" + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.58.10" + } + }, + "name": "172.16.58.10", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "nce-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 15 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "200", + "name": "200", + "type": "optical", + "address_ip": "0.0.0.0", + "address_prefix": "24" + }, + { + "uuid": "201", + "name": "201", + "type": "optical", + "address_ip": "0.0.0.0", + "address_prefix": "24" + }, + { + "uuid": "500", + "name": "500", + "type": "optical", + "address_ip": "128.32.33.2", + "address_prefix": "24" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.10" + } + }, + "name": "172.16.61.10", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "nce-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 15 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "200", + "name": "200", + "type": "optical", + "address_ip": "0.0.0.0", + "address_prefix": "24" + }, + { + "uuid": "500", + "name": "500", + "type": "optical", + "address_ip": "128.32.33.2", + "address_prefix": "24" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.11" + } + }, + "name": "172.16.61.11", + "device_type": "emu-packet-router", + "controller_id": { + "device_uuid": { + "uuid": "nce-controller" + } + }, + "device_operational_status": 1, + "device_drivers": [ + 15 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "200", + "name": "200", + "type": "optical", + "address_ip": "0.0.0.0", + "address_prefix": "24" + }, + { + "uuid": "500", + "name": "500", + "type": "optical", + "address_ip": "128.32.33.2", + "address_prefix": "24" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.104.221" + } + }, + "device_type": "emu-datacenter", + "device_drivers": [ + 0 + ], + "device_endpoints": [], + "device_operational_status": 1, + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "sample_types": [], + "type": "copper", + "uuid": "eth0" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.104.222" + } + }, + "device_type": "emu-datacenter", + "device_drivers": [ + 0 + ], + "device_endpoints": [], + "device_operational_status": 1, + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "sample_types": [], + "type": "copper", + "uuid": "eth0" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.204.220" + } + }, + "device_type": "emu-datacenter", + "device_drivers": [ + 0 + ], + "device_endpoints": [], + "device_operational_status": 1, + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "sample_types": [], + "type": "optical", + "uuid": "500" + }, + { + "sample_types": [], + "type": "optical", + "uuid": "200" + }, + { + "sample_types": [], + "type": "optical", + "uuid": "201" + } + ] + } + } + } + ] + } + } + ], + "links": [ + { + "link_id": { + "link_uuid": { + "uuid": "nce-controller/mgmt==172.16.61.11/mgmt" + } + }, + "name": "nce-controller/mgmt==172.16.61.11/mgmt", + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "nce-controller" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.11" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "nce-controller/mgmt==172.16.61.10/mgmt" + } + }, + "name": "nce-controller/mgmt==172.16.61.10/mgmt", + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "nce-controller" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.10" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "nce-controller/mgmt==172.16.58.10/mgmt" + } + }, + "name": "nce-controller/mgmt==172.16.58.10/mgmt", + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "nce-controller" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.58.10" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "agg-net-controller/mgmt==172.16.185.33/mgmt" + } + }, + "name": "agg-net-controller/mgmt==172.16.185.33/mgmt", + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "agg-net-controller" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "agg-net-controller/mgmt==172.16.185.31/mgmt" + } + }, + "name": "agg-net-controller/mgmt==172.16.185.31/mgmt", + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "agg-net-controller" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "agg-net-controller/mgmt==172.16.182.25/mgmt" + } + }, + "name": "agg-net-controller/mgmt==172.16.182.25/mgmt", + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "agg-net-controller" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "agg-net-controller/mgmt==172.16.182.25/mgmt" + } + }, + "name": "agg-net-controller/mgmt==172.16.182.25/mgmt", + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "agg-net-controller" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "mgmt" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.182.25-500" + } + }, + "name": "172.16.182.25-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.33-500" + } + }, + "name": "172.16.185.33-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.182.25-501" + } + }, + "name": "172.16.182.25-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.31-501" + } + }, + "name": "172.16.185.31-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.31-500" + } + }, + "name": "172.16.185.31-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.32-500" + } + }, + "name": "172.16.185.32-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.33-501" + } + }, + "name": "172.16.185.33-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.32-501" + } + }, + "name": "172.16.185.32-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.32-200" + } + }, + "name": "172.16.185.32-200", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "200" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.204.220" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.204.220-500" + } + }, + "name": "172.16.204.220-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.204.220" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "200" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.182.25-200" + } + }, + "name": "172.16.182.25-200", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "200" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.58.10" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.58.10-500" + } + }, + "name": "172.16.58.10-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.58.10" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "200" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.58.10-200" + } + }, + "name": "172.16.58.10-200", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.58.10" + } + }, + "endpoint_uuid": { + "uuid": "200" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.10" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.61.10-500" + } + }, + "name": "172.16.61.10-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.10" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.58.10" + } + }, + "endpoint_uuid": { + "uuid": "200" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.58.10-201" + } + }, + "name": "172.16.58.10-201", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.58.10" + } + }, + "endpoint_uuid": { + "uuid": "201" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.11" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.61.11-500" + } + }, + "name": "172.16.61.11-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.11" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.58.10" + } + }, + "endpoint_uuid": { + "uuid": "201" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.61.10-200" + } + }, + "name": "172.16.61.10-200", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.10" + } + }, + "endpoint_uuid": { + "uuid": "200" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.104.221" + } + }, + "endpoint_uuid": { + "uuid": "eth0" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.104.221-eth0" + } + }, + "name": "172.16.104.221-eth0", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.104.221" + } + }, + "endpoint_uuid": { + "uuid": "eth0" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.10" + } + }, + "endpoint_uuid": { + "uuid": "200" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.61.11-200" + } + }, + "name": "172.16.61.11-200", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.11" + } + }, + "endpoint_uuid": { + "uuid": "200" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.104.222" + } + }, + "endpoint_uuid": { + "uuid": "eth0" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.104.222-eth0" + } + }, + "name": "172.16.104.222-eth0", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.104.222" + } + }, + "endpoint_uuid": { + "uuid": "eth0" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.61.11" + } + }, + "endpoint_uuid": { + "uuid": "200" + } + } + ] + } + ] +} diff --git a/src/nbi/tests/data/ip-net-descriptor.json b/src/nbi/tests/data/ip-net-descriptor.json new file mode 100644 index 0000000000000000000000000000000000000000..dafeeb5bc22dd457fa7b924a30feac660b38f491 --- /dev/null +++ b/src/nbi/tests/data/ip-net-descriptor.json @@ -0,0 +1,535 @@ +{ + "contexts": [ + { + "context_id": { + "context_uuid": { + "uuid": "admin" + } + } + } + ], + "topologies": [ + { + "topology_id": { + "context_id": { + "context_uuid": { + "uuid": "admin" + } + }, + "topology_uuid": { + "uuid": "admin" + } + } + } + ], + "devices": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "name": "172.16.182.25", + "device_type": "emu-packet-router", + "device_operational_status": 1, + "device_drivers": [ + 0 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "200", + "name": "200", + "type": "optical" + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "name": "172.16.185.31", + "device_type": "emu-packet-router", + "device_operational_status": 1, + "device_drivers": [ + 0 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "name": "172.16.185.33", + "device_type": "emu-packet-router", + "device_operational_status": 1, + "device_drivers": [ + 0 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "name": "172.16.185.32", + "device_type": "emu-packet-router", + "device_operational_status": 1, + "device_drivers": [ + 0 + ], + "device_config": { + "config_rules": [ + { + "action": 1, + "custom": { + "resource_key": "_connect/address", + "resource_value": "127.0.0.1" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/port", + "resource_value": "0" + } + }, + { + "action": 1, + "custom": { + "resource_key": "_connect/settings", + "resource_value": { + "endpoints": [ + { + "uuid": "mgmt", + "name": "mgmt", + "type": "mgmt" + }, + { + "uuid": "200", + "name": "200", + "type": "optical" + }, + { + "uuid": "500", + "name": "500", + "type": "optical" + }, + { + "uuid": "501", + "name": "501", + "type": "optical" + } + ] + } + } + } + ] + } + } + ], + "links": [ + { + "link_id": { + "link_uuid": { + "uuid": "172.16.182.25-500" + } + }, + "name": "172.16.182.25-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.33-500" + } + }, + "name": "172.16.185.33-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.182.25-501" + } + }, + "name": "172.16.182.25-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.31-501" + } + }, + "name": "172.16.185.31-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.182.25" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.31-500" + } + }, + "name": "172.16.185.31-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.32-500" + } + }, + "name": "172.16.185.32-500", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.31" + } + }, + "endpoint_uuid": { + "uuid": "500" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.33-501" + } + }, + "name": "172.16.185.33-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + }, + { + "link_id": { + "link_uuid": { + "uuid": "172.16.185.32-501" + } + }, + "name": "172.16.185.32-501", + "attributes": { + "total_capacity_gbps": 10, + "used_capacity_gbps": 0 + }, + "link_endpoint_ids": [ + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.32" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + }, + { + "device_id": { + "device_uuid": { + "uuid": "172.16.185.33" + } + }, + "endpoint_uuid": { + "uuid": "501" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/src/nbi/tests/data/slice/post_connection_group_to_network_slice1.json b/src/nbi/tests/data/slice/post_connection_group_to_network_slice1.json new file mode 100644 index 0000000000000000000000000000000000000000..d39a837bd8c3719463e8ecfd3fbfc2d25111afef --- /dev/null +++ b/src/nbi/tests/data/slice/post_connection_group_to_network_slice1.json @@ -0,0 +1,62 @@ +{ + "connection-group": [ + { + "id": "line2", + "connectivity-type": "point-to-point", + "connectivity-construct": [ + { + "id": 1, + "p2p-sender-sdp": "1", + "p2p-receiver-sdp": "3", + "service-slo-sle-policy": { + "slo-policy": { + "metric-bound": [ + { + "metric-type": "ietf-network-slice-service:one-way-delay-maximum", + "metric-unit": "milliseconds", + "bound": "10" + }, + { + "metric-type": "ietf-network-slice-service:one-way-bandwidth", + "metric-unit": "Mbps", + "bound": "5000" + }, + { + "metric-type": "ietf-network-slice-service:two-way-packet-loss", + "metric-unit": "percentage", + "percentile-value": "0.001" + } + ] + } + } + }, + { + "id": 2, + "p2p-sender-sdp": "3", + "p2p-receiver-sdp": "1", + "service-slo-sle-policy": { + "slo-policy": { + "metric-bound": [ + { + "metric-type": "ietf-network-slice-service:one-way-delay-maximum", + "metric-unit": "milliseconds", + "bound": "20" + }, + { + "metric-type": "ietf-network-slice-service:one-way-bandwidth", + "metric-unit": "Mbps", + "bound": "1000" + }, + { + "metric-type": "ietf-network-slice-service:two-way-packet-loss", + "metric-unit": "percentage", + "percentile-value": "0.001" + } + ] + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/src/nbi/tests/data/slice/post_connection_group_to_network_slice2.json b/src/nbi/tests/data/slice/post_connection_group_to_network_slice2.json new file mode 100644 index 0000000000000000000000000000000000000000..d39a837bd8c3719463e8ecfd3fbfc2d25111afef --- /dev/null +++ b/src/nbi/tests/data/slice/post_connection_group_to_network_slice2.json @@ -0,0 +1,62 @@ +{ + "connection-group": [ + { + "id": "line2", + "connectivity-type": "point-to-point", + "connectivity-construct": [ + { + "id": 1, + "p2p-sender-sdp": "1", + "p2p-receiver-sdp": "3", + "service-slo-sle-policy": { + "slo-policy": { + "metric-bound": [ + { + "metric-type": "ietf-network-slice-service:one-way-delay-maximum", + "metric-unit": "milliseconds", + "bound": "10" + }, + { + "metric-type": "ietf-network-slice-service:one-way-bandwidth", + "metric-unit": "Mbps", + "bound": "5000" + }, + { + "metric-type": "ietf-network-slice-service:two-way-packet-loss", + "metric-unit": "percentage", + "percentile-value": "0.001" + } + ] + } + } + }, + { + "id": 2, + "p2p-sender-sdp": "3", + "p2p-receiver-sdp": "1", + "service-slo-sle-policy": { + "slo-policy": { + "metric-bound": [ + { + "metric-type": "ietf-network-slice-service:one-way-delay-maximum", + "metric-unit": "milliseconds", + "bound": "20" + }, + { + "metric-type": "ietf-network-slice-service:one-way-bandwidth", + "metric-unit": "Mbps", + "bound": "1000" + }, + { + "metric-type": "ietf-network-slice-service:two-way-packet-loss", + "metric-unit": "percentage", + "percentile-value": "0.001" + } + ] + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/src/nbi/tests/data/slice/post_match_criteria_to_sdp1_in_slice1.json b/src/nbi/tests/data/slice/post_match_criteria_to_sdp1_in_slice1.json new file mode 100644 index 0000000000000000000000000000000000000000..16a36d45b86230b27eafa45a612b95c248a7b3ac --- /dev/null +++ b/src/nbi/tests/data/slice/post_match_criteria_to_sdp1_in_slice1.json @@ -0,0 +1,40 @@ +{ + "match-criterion": [ + { + "index": 2, + "match-type": [ + { + "type": "ietf-network-slice-service:vlan", + "value": [ + "101" + ] + }, + { + "type": "ietf-network-slice-service:source-ip-prefix", + "value": [ + "172.1.101.22/24" + ] + }, + { + "type": "ietf-network-slice-service:source-tcp-port", + "value": [ + "10200" + ] + }, + { + "type": "ietf-network-slice-service:destination-ip-prefix", + "value": [ + "172.16.104.222/24" + ] + }, + { + "type": "ietf-network-slice-service:destination-tcp-port", + "value": [ + "10500" + ] + } + ], + "target-connection-group-id": "line2" + } + ] +} \ No newline at end of file diff --git a/src/nbi/tests/data/slice/post_match_criteria_to_sdp1_in_slice2.json b/src/nbi/tests/data/slice/post_match_criteria_to_sdp1_in_slice2.json new file mode 100644 index 0000000000000000000000000000000000000000..8ceefdc2f2471af225143e5a1def2d7ba71e2ab1 --- /dev/null +++ b/src/nbi/tests/data/slice/post_match_criteria_to_sdp1_in_slice2.json @@ -0,0 +1,40 @@ +{ + "match-criterion": [ + { + "index": 2, + "match-type": [ + { + "type": "ietf-network-slice-service:vlan", + "value": [ + "201" + ] + }, + { + "type": "ietf-network-slice-service:source-ip-prefix", + "value": [ + "172.1.201.22/24" + ] + }, + { + "type": "ietf-network-slice-service:source-tcp-port", + "value": [ + "10200" + ] + }, + { + "type": "ietf-network-slice-service:destination-ip-prefix", + "value": [ + "172.16.104.222/24" + ] + }, + { + "type": "ietf-network-slice-service:destination-tcp-port", + "value": [ + "10500" + ] + } + ], + "target-connection-group-id": "line2" + } + ] +} \ No newline at end of file diff --git a/src/nbi/tests/data/slice/post_network_slice1.json b/src/nbi/tests/data/slice/post_network_slice1.json new file mode 100644 index 0000000000000000000000000000000000000000..e6e0ee90a25ff12f73c8f8896f9c2c74ab6b4019 --- /dev/null +++ b/src/nbi/tests/data/slice/post_network_slice1.json @@ -0,0 +1,188 @@ +{ + "slice-service": [ + { + "id": "slice1", + "description": "network slice 1, connect to VM1", + "sdps": { + "sdp": [ + { + "id": "1", + "node-id": "172.16.204.220", + "sdp-ip-address": [ + "172.16.204.220" + ], + "service-match-criteria": { + "match-criterion": [ + { + "index": 1, + "match-type": [ + { + "type": "ietf-network-slice-service:vlan", + "value": [ + "101" + ] + }, + { + "type": "ietf-network-slice-service:destination-ip-prefix", + "value": [ + "172.16.104.221/24" + ] + }, + { + "type": "ietf-network-slice-service:destination-tcp-port", + "value": [ + "10500" + ] + }, + { + "type": "ietf-network-slice-service:source-ip-prefix", + "value": [ + "172.1.101.22/24" + ] + }, + { + "type": "ietf-network-slice-service:source-tcp-port", + "value": [ + "10200" + ] + } + ], + "target-connection-group-id": "line1" + } + ] + }, + "attachment-circuits": { + "attachment-circuit": [ + { + "id": "AC POP to VM1", + "description": "AC VM1 connected to POP", + "ac-node-id": "172.16.204.220", + "ac-tp-id": "200" + } + ] + } + }, + { + "id": "2", + "node-id": "172.16.61.10", + "sdp-ip-address": [ + "172.16.61.10" + ], + "service-match-criteria": { + "match-criterion": [ + { + "index": 1, + "match-type": [ + { + "type": "ietf-network-slice-service:vlan", + "value": [ + "21" + ] + }, + { + "type": "ietf-network-slice-service:source-ip-prefix", + "value": [ + "172.16.104.221/24" + ] + }, + { + "type": "ietf-network-slice-service:source-tcp-port", + "value": [ + "10500" + ] + }, + { + "type": "ietf-network-slice-service:destination-ip-prefix", + "value": [ + "172.1.101.22/24" + ] + }, + { + "type": "ietf-network-slice-service:destination-tcp-port", + "value": [ + "10200" + ] + } + ], + "target-connection-group-id": "line1" + } + ] + }, + "attachment-circuits": { + "attachment-circuit": [ + { + "id": "AC ONT", + "description": "AC connected to PC1", + "ac-node-id": "172.16.61.10", + "ac-tp-id": "200" + } + ] + } + } + ] + }, + "connection-groups": { + "connection-group": [ + { + "id": "line1", + "connectivity-type": "point-to-point", + "connectivity-construct": [ + { + "id": 1, + "p2p-sender-sdp": "1", + "p2p-receiver-sdp": "2", + "service-slo-sle-policy": { + "slo-policy": { + "metric-bound": [ + { + "metric-type": "ietf-network-slice-service:one-way-delay-maximum", + "metric-unit": "milliseconds", + "bound": "10" + }, + { + "metric-type": "ietf-network-slice-service:one-way-bandwidth", + "metric-unit": "Mbps", + "bound": "5000" + }, + { + "metric-type": "ietf-network-slice-service:two-way-packet-loss", + "metric-unit": "percentage", + "percentile-value": "0.001" + } + ] + } + } + }, + { + "id": 2, + "p2p-sender-sdp": "2", + "p2p-receiver-sdp": "1", + "service-slo-sle-policy": { + "slo-policy": { + "metric-bound": [ + { + "metric-type": "ietf-network-slice-service:one-way-delay-maximum", + "metric-unit": "milliseconds", + "bound": "20" + }, + { + "metric-type": "ietf-network-slice-service:one-way-bandwidth", + "metric-unit": "Mbps", + "bound": "1000" + }, + { + "metric-type": "ietf-network-slice-service:two-way-packet-loss", + "metric-unit": "percentage", + "percentile-value": "0.001" + } + ] + } + } + } + ] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/nbi/tests/data/slice/post_network_slice2.json b/src/nbi/tests/data/slice/post_network_slice2.json new file mode 100644 index 0000000000000000000000000000000000000000..97e6ade27449be0a3816085aa31b707ffbb6f813 --- /dev/null +++ b/src/nbi/tests/data/slice/post_network_slice2.json @@ -0,0 +1,189 @@ +{ + "slice-service": [ + { + "id": "slice2", + "description": "network slice 2, connect to VM2", + "sdps": { + "sdp": [ + { + "id": "1", + "node-id": "172.16.204.220", + "sdp-ip-address": [ + "172.16.204.220" + ], + "service-match-criteria": { + "match-criterion": [ + { + "index": 1, + "match-type": [ + { + "type": "ietf-network-slice-service:vlan", + "value": [ + "201" + ] + }, + { + "type": "ietf-network-slice-service:destination-ip-prefix", + "value": [ + "172.16.104.221/24" + ] + }, + { + "type": "ietf-network-slice-service:destination-tcp-port", + "value": [ + "10500" + ] + }, + { + "type": "ietf-network-slice-service:source-ip-prefix", + "value": [ + "172.1.201.22/24" + ] + }, + { + "type": "ietf-network-slice-service:source-tcp-port", + "value": [ + "10200" + ] + } + ], + "target-connection-group-id": "line1" + } + ] + }, + "attachment-circuits": { + "attachment-circuit": [ + { + "id": "AC POP to VM2", + "description": "AC VM2 connected to POP", + "ac-node-id": "172.16.204.220", + "ac-tp-id": "201" + } + ] + } + }, + { + "id": "2", + "node-id": "172.16.61.10", + "sdp-ip-address": [ + "172.16.61.10" + ], + "service-match-criteria": { + "match-criterion": [ + { + "index": 1, + "match-type": [ + { + "type": "ietf-network-slice-service:vlan", + "value": [ + "31" + ] + }, + { + "type": "ietf-network-slice-service:source-ip-prefix", + "value": [ + "172.16.104.221/24" + ] + }, + { + "type": "ietf-network-slice-service:source-tcp-port", + "value": [ + "10500" + ] + }, + { + "type": "ietf-network-slice-service:destination-ip-prefix", + "value": [ + "172.1.201.22/24" + ] + }, + { + "type": "ietf-network-slice-service:destination-tcp-port", + "value": [ + "10200" + ] + } + ], + "target-connection-group-id": "line1" + } + ] + }, + "attachment-circuits": { + "attachment-circuit": [ + { + "id": "AC ONT", + "description": "AC connected to PC", + "ac-node-id": "172.16.61.10", + "ac-tp-id": "200", + "ac-ipv4-address": "172.16.61.10" + } + ] + } + } + ] + }, + "connection-groups": { + "connection-group": [ + { + "id": "line1", + "connectivity-type": "point-to-point", + "connectivity-construct": [ + { + "id": 1, + "p2p-sender-sdp": "1", + "p2p-receiver-sdp": "2", + "service-slo-sle-policy": { + "slo-policy": { + "metric-bound": [ + { + "metric-type": "ietf-network-slice-service:one-way-delay-maximum", + "metric-unit": "milliseconds", + "bound": "10" + }, + { + "metric-type": "ietf-network-slice-service:one-way-bandwidth", + "metric-unit": "Mbps", + "bound": "5000" + }, + { + "metric-type": "ietf-network-slice-service:two-way-packet-loss", + "metric-unit": "percentage", + "percentile-value": "0.001" + } + ] + } + } + }, + { + "id": 2, + "p2p-sender-sdp": "2", + "p2p-receiver-sdp": "1", + "service-slo-sle-policy": { + "slo-policy": { + "metric-bound": [ + { + "metric-type": "ietf-network-slice-service:one-way-delay-maximum", + "metric-unit": "milliseconds", + "bound": "20" + }, + { + "metric-type": "ietf-network-slice-service:one-way-bandwidth", + "metric-unit": "Mbps", + "bound": "1000" + }, + { + "metric-type": "ietf-network-slice-service:two-way-packet-loss", + "metric-unit": "percentage", + "percentile-value": "0.001" + } + ] + } + } + } + ] + } + ] + } + } + ] +} diff --git a/src/nbi/tests/data/slice/post_sdp_to_network_slice1.json b/src/nbi/tests/data/slice/post_sdp_to_network_slice1.json new file mode 100644 index 0000000000000000000000000000000000000000..bd3895fc4ae5a9a0b2059be3f6b31a05451abd22 --- /dev/null +++ b/src/nbi/tests/data/slice/post_sdp_to_network_slice1.json @@ -0,0 +1,61 @@ +{ + "sdp": [ + { + "id": "3", + "node-id": "172.16.61.11", + "sdp-ip-address": [ + "172.16.61.11" + ], + "service-match-criteria": { + "match-criterion": [ + { + "index": 1, + "match-type": [ + { + "type": "ietf-network-slice-service:vlan", + "value": [ + "21" + ] + }, + { + "type": "ietf-network-slice-service:source-ip-prefix", + "value": [ + "172.16.104.222/24" + ] + }, + { + "type": "ietf-network-slice-service:source-tcp-port", + "value": [ + "10500" + ] + }, + { + "type": "ietf-network-slice-service:destination-ip-prefix", + "value": [ + "172.1.101.22/24" + ] + }, + { + "type": "ietf-network-slice-service:destination-tcp-port", + "value": [ + "10200" + ] + } + ], + "target-connection-group-id": "line2" + } + ] + }, + "attachment-circuits": { + "attachment-circuit": [ + { + "id": "AC ONT", + "description": "AC connected to PC2", + "ac-node-id": "172.16.61.11", + "ac-tp-id": "200" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/nbi/tests/data/slice/post_sdp_to_network_slice2.json b/src/nbi/tests/data/slice/post_sdp_to_network_slice2.json new file mode 100644 index 0000000000000000000000000000000000000000..0b147125bd7eb3efc84c87bebab919639782f760 --- /dev/null +++ b/src/nbi/tests/data/slice/post_sdp_to_network_slice2.json @@ -0,0 +1,62 @@ +{ + "sdp": [ + { + "id": "3", + "node-id": "172.16.61.11", + "sdp-ip-address": [ + "172.16.61.11" + ], + "service-match-criteria": { + "match-criterion": [ + { + "index": 1, + "match-type": [ + { + "type": "ietf-network-slice-service:vlan", + "value": [ + "31" + ] + }, + { + "type": "ietf-network-slice-service:source-ip-prefix", + "value": [ + "172.16.104.222/24" + ] + }, + { + "type": "ietf-network-slice-service:source-tcp-port", + "value": [ + "10500" + ] + }, + { + "type": "ietf-network-slice-service:destination-ip-prefix", + "value": [ + "172.1.201.22/24" + ] + }, + { + "type": "ietf-network-slice-service:destination-tcp-port", + "value": [ + "10200" + ] + } + ], + "target-connection-group-id": "line2" + } + ] + }, + "attachment-circuits": { + "attachment-circuit": [ + { + "id": "AC ONT", + "description": "AC connected to PC2", + "ac-node-id": "172.16.61.11", + "ac-tp-id": "200", + "ac-ipv4-address": "172.16.61.11" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/nbi/tests/test_slice_2.py b/src/nbi/tests/test_slice_2.py new file mode 100644 index 0000000000000000000000000000000000000000..5722e3d922a79bd169fc80a5374c4daab4f5d7d9 --- /dev/null +++ b/src/nbi/tests/test_slice_2.py @@ -0,0 +1,205 @@ +# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +from typing import Optional + +from common.proto.context_pb2 import ConfigRule, ServiceConfig, SliceList +from context.client.ContextClient import ContextClient +from nbi.service.rest_server.nbi_plugins.ietf_network_slice.ietf_slice_handler import ( + IETFSliceHandler, +) + + +def get_custom_config_rule( + service_config: ServiceConfig, resource_key: str +) -> Optional[ConfigRule]: + for cr in service_config.config_rules: + if ( + cr.WhichOneof("config_rule") == "custom" + and cr.custom.resource_key == resource_key + ): + return cr + + +RUNNING_RESOURCE_KEY = "running_ietf_slice" +CANDIDATE_RESOURCE_KEY = "candidate_ietf_slice" + +context_client = ContextClient() + +with open("nbi/tests/data/slice/post_network_slice1.json", mode="r") as f: + post_slice_request = json.load(f) + +with open("nbi/tests/data/slice/post_sdp_to_network_slice1.json", mode="r") as f: + post_sdp_request = json.load(f) + +with open( + "nbi/tests/data/slice/post_connection_group_to_network_slice1.json", mode="r" +) as f: + post_connection_group_request = json.load(f) + +with open( + "nbi/tests/data/slice/post_match_criteria_to_sdp1_in_slice1.json", mode="r" +) as f: + post_match_criteria_request = json.load(f) + +slice_1 = None + + +def select_slice(*args) -> SliceList: + slice_list = SliceList() + slice_list.slices.extend([slice_1]) + return slice_list + + +def test_create_slice(): + global slice_1 + + slice_1 = IETFSliceHandler.create_slice_service(post_slice_request) + candidate_ietf_data = json.loads( + get_custom_config_rule( + slice_1.slice_config, CANDIDATE_RESOURCE_KEY + ).custom.resource_value + ) + assert candidate_ietf_data["network-slice-services"] == post_slice_request + assert slice_1.slice_endpoint_ids[0].device_id.device_uuid.uuid == "172.16.204.220" + assert slice_1.slice_endpoint_ids[1].device_id.device_uuid.uuid == "172.16.61.10" + assert slice_1.slice_id.slice_uuid.uuid == "slice1" + + +def test_create_sdp(monkeypatch): + global slice_1 + + monkeypatch.setattr(context_client, "SelectSlice", select_slice) + + slice_1 = IETFSliceHandler.create_sdp(post_sdp_request, "slice1", context_client) + candidate_ietf_data = json.loads( + get_custom_config_rule( + slice_1.slice_config, CANDIDATE_RESOURCE_KEY + ).custom.resource_value + ) + slice_services = candidate_ietf_data["network-slice-services"]["slice-service"] + slice_service = slice_services[0] + slice_sdps = slice_service["sdps"]["sdp"] + assert len(slice_sdps) == 3 + + +def test_create_connection_group(monkeypatch): + global slice_1 + + monkeypatch.setattr(context_client, "SelectSlice", select_slice) + + slice_1 = IETFSliceHandler.create_connection_group( + post_connection_group_request, "slice1", context_client + ) + candidate_ietf_data = json.loads( + get_custom_config_rule( + slice_1.slice_config, CANDIDATE_RESOURCE_KEY + ).custom.resource_value + ) + slice_services = candidate_ietf_data["network-slice-services"]["slice-service"] + slice_service = slice_services[0] + slice_connection_groups = slice_service["connection-groups"]["connection-group"] + + assert slice_connection_groups[0]["id"] == "line1" + assert slice_connection_groups[1]["id"] == "line2" + assert len(slice_connection_groups) == 2 + + +def test_create_match_criteria(monkeypatch): + global slice_1 + + monkeypatch.setattr(context_client, "SelectSlice", select_slice) + + slice_1 = IETFSliceHandler.create_match_criteria( + post_match_criteria_request, "slice1", "1", context_client + ) + candidate_ietf_data = json.loads( + get_custom_config_rule( + slice_1.slice_config, CANDIDATE_RESOURCE_KEY + ).custom.resource_value + ) + slice_services = candidate_ietf_data["network-slice-services"]["slice-service"] + slice_service = slice_services[0] + slice_sdps = slice_service["sdps"]["sdp"] + sdp1_match_criteria = slice_sdps[0]["service-match-criteria"]["match-criterion"] + + slice_1 = IETFSliceHandler.copy_candidate_ietf_slice_data_to_running("slice1", context_client) + assert len(sdp1_match_criteria) == 2 + assert sdp1_match_criteria[0]["target-connection-group-id"] == "line1" + assert sdp1_match_criteria[1]["target-connection-group-id"] == "line2" + assert slice_1.slice_endpoint_ids[0].device_id.device_uuid.uuid == "172.16.204.220" + assert slice_1.slice_endpoint_ids[1].device_id.device_uuid.uuid == "172.16.61.11" + + +def test_delete_sdp(monkeypatch): + global slice_1 + + monkeypatch.setattr(context_client, "SelectSlice", select_slice) + + slice_1 = IETFSliceHandler.delete_sdp("slice1", "3", context_client) + candidate_ietf_data = json.loads( + get_custom_config_rule( + slice_1.slice_config, CANDIDATE_RESOURCE_KEY + ).custom.resource_value + ) + slice_services = candidate_ietf_data["network-slice-services"]["slice-service"] + slice_service = slice_services[0] + slice_sdps = slice_service["sdps"]["sdp"] + assert len(slice_sdps) == 2 + assert "3" not in (sdp["id"] for sdp in slice_sdps) + + +def test_delete_connection_group(monkeypatch): + global slice_1 + + monkeypatch.setattr(context_client, "SelectSlice", select_slice) + running_ietf_data = json.loads( + get_custom_config_rule( + slice_1.slice_config, RUNNING_RESOURCE_KEY + ).custom.resource_value + ) + slice_1 = IETFSliceHandler.delete_connection_group( + "slice1", "line2", context_client + ) + + candidate_ietf_data = json.loads( + get_custom_config_rule( + slice_1.slice_config, CANDIDATE_RESOURCE_KEY + ).custom.resource_value + ) + slice_services = candidate_ietf_data["network-slice-services"]["slice-service"] + slice_service = slice_services[0] + slice_connection_groups = slice_service["connection-groups"]["connection-group"] + assert len(slice_connection_groups) == 1 + assert slice_connection_groups[0]["id"] == "line1" + + +def test_delete_match_criteria(monkeypatch): + global slice_1 + + monkeypatch.setattr(context_client, "SelectSlice", select_slice) + + slice_1 = IETFSliceHandler.delete_match_criteria("slice1", "1", 2, context_client) + candidate_ietf_data = json.loads( + get_custom_config_rule( + slice_1.slice_config, CANDIDATE_RESOURCE_KEY + ).custom.resource_value + ) + slice_services = candidate_ietf_data["network-slice-services"]["slice-service"] + slice_service = slice_services[0] + slice_sdps = slice_service["sdps"]["sdp"] + sdp1_match_criteria = slice_sdps[0]["service-match-criteria"]["match-criterion"] + assert len(sdp1_match_criteria) == 1 + assert sdp1_match_criteria[0]["target-connection-group-id"] == "line1" diff --git a/src/service/requirements.in b/src/service/requirements.in index 3f8d2a35453691420a9469dfffd0a0d2648c6397..d0dd6dcfe13610fc315a50437fb5f3e094b4ee5e 100644 --- a/src/service/requirements.in +++ b/src/service/requirements.in @@ -13,6 +13,7 @@ # limitations under the License. +deepdiff==6.7.* anytree==2.8.0 geopy==2.3.0 netaddr==0.9.0 diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index 78f084605bcd759825975cb7f11abc659506755b..bb96f28351d6c29de43d7dbb8a42db2a095795c3 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -44,6 +44,7 @@ DEVICE_DRIVER_VALUES = { DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN, DeviceDriverEnum.DEVICEDRIVER_OC, DeviceDriverEnum.DEVICEDRIVER_QKD, + DeviceDriverEnum.DEVICEDRIVER_IETF_L3VPN, } # Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index f93cf011fe02139ae350b91eab52eb71ded0574d..b4d9f39ded994e629eed9847b94b359bbc9b027b 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -16,6 +16,7 @@ from common.proto.context_pb2 import DeviceDriverEnum, ServiceTypeEnum from ..service_handler_api.FilterFields import FilterFieldEnum from .l2nm_emulated.L2NMEmulatedServiceHandler import L2NMEmulatedServiceHandler from .l2nm_ietfl2vpn.L2NM_IETFL2VPN_ServiceHandler import L2NM_IETFL2VPN_ServiceHandler +from .l3nm_ietfl3vpn.L3NM_IETFL3VPN_ServiceHandler import L3NM_IETFL3VPN_ServiceHandler from .l2nm_openconfig.L2NMOpenConfigServiceHandler import L2NMOpenConfigServiceHandler from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler @@ -66,6 +67,12 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN, } ]), + (L3NM_IETFL3VPN_ServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L3NM, + FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_L3VPN, + } + ]), (TapiServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, diff --git a/src/service/service/service_handlers/l3nm_ietfl3vpn/ConfigRules.py b/src/service/service/service_handlers/l3nm_ietfl3vpn/ConfigRules.py new file mode 100644 index 0000000000000000000000000000000000000000..c5638fc104c253be20ef1bbeb6c69a4392095ad2 --- /dev/null +++ b/src/service/service/service_handlers/l3nm_ietfl3vpn/ConfigRules.py @@ -0,0 +1,316 @@ +# 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. + +from typing import Dict, List, Tuple, TypedDict + +from common.proto.context_pb2 import Link +from common.tools.object_factory.ConfigRule import ( + json_config_rule_delete, + json_config_rule_set, +) +from context.client.ContextClient import ContextClient + + +class LANPrefixesDict(TypedDict): + lan: str + lan_tag: str + + +SITE_NETWORK_ACCESS_TYPE = "ietf-l3vpn-svc:multipoint" + + +def create_site_dict( + site_id: str, + site_location: str, + device_uuid: str, + endpoint_uuid: str, + service_uuid: str, + role: str, + management_type: str, + ce_address: str, + pe_address: str, + ce_pe_network_prefix: int, + mtu: int, + input_bw: int, + output_bw: int, + qos_profile_id: str, + qos_profile_direction: str, + qos_profile_latency: int, + qos_profile_bw_guarantee: int, + lan_prefixes: List[LANPrefixesDict], +) -> Dict: + """ + Helper function that creates a dictionary representing a single 'site' + entry (including management, locations, devices, routing-protocols, and + site-network-accesses). + """ + site_lan_prefixes = [ + { + "lan": lp["lan"], + "lan-tag": lp["lan_tag"], + "next-hop": ce_address, + } + for lp in lan_prefixes + ] + + return { + "site-id": site_id, + "management": {"type": management_type}, + "locations": {"location": [{"location-id": site_location}]}, + "devices": { + "device": [ + { + "device-id": device_uuid, + "location": site_location, + } + ] + }, + "routing-protocols": { + "routing-protocol": [ + { + "type": "ietf-l3vpn-svc:static", + "static": { + "cascaded-lan-prefixes": { + "ipv4-lan-prefixes": site_lan_prefixes + } + }, + } + ] + }, + "site-network-accesses": { + "site-network-access": [ + { + "site-network-access-id": endpoint_uuid, + "site-network-access-type": SITE_NETWORK_ACCESS_TYPE, + "device-reference": device_uuid, + "vpn-attachment": { + "vpn-id": service_uuid, + "site-role": role, + }, + "ip-connection": { + "ipv4": { + "address-allocation-type": "ietf-l3vpn-svc:static-address", + "addresses": { + "provider-address": pe_address, + "customer-address": ce_address, + "prefix-length": ce_pe_network_prefix, + }, + } + }, + "service": { + "svc-mtu": mtu, + "svc-input-bandwidth": input_bw, + "svc-output-bandwidth": output_bw, + "qos": { + "qos-profile": { + "classes": { + "class": [ + { + "class-id": qos_profile_id, + "direction": qos_profile_direction, + "latency": { + "latency-boundary": qos_profile_latency + }, + "bandwidth": { + "guaranteed-bw-percent": qos_profile_bw_guarantee + }, + } + ] + } + } + }, + }, + } + ] + }, + } + + +def setup_config_rules( + service_uuid: str, json_settings: Dict, operation_type: str +) -> List[Dict]: + # --- Extract common or required fields for the source site --- + src_device_uuid: str = json_settings["src_device_name"] + src_endpoint_uuid: str = json_settings["src_endpoint_name"] + src_site_location: str = json_settings["src_site_location"] + src_ipv4_lan_prefixes: list[LANPrefixesDict] = json_settings.get( + "src_ipv4_lan_prefixes" + ) + src_site_id: str = json_settings.get("src_site_id", f"site_{src_site_location}") + src_management_type: str = json_settings.get( + "src_management_type", "ietf-l3vpn-svc:provider-managed" + ) + if src_management_type != "ietf-l3vpn-svc:provider-managed": + raise Exception("management type %s not supported", src_management_type) + + src_role: str = "ietf-l3vpn-svc:hub-role" + src_ce_address: str = json_settings["src_ce_address"] + src_pe_address: str = json_settings["src_pe_address"] + src_ce_pe_network_prefix: int = json_settings["src_ce_pe_network_prefix"] + src_mtu: int = json_settings["src_mtu"] + src_input_bw: int = json_settings["src_input_bw"] + src_output_bw: int = json_settings["src_output_bw"] + src_qos_profile_id = "qos-realtime" + src_qos_profile_direction = "ietf-l3vpn-svc:both" + src_qos_profile_latency: int = json_settings["src_qos_profile_latency"] + src_qos_profile_bw_guarantee: int = json_settings.get( + "src_qos_profile_bw_guarantee", 100 + ) + + # --- Extract common or required fields for the destination site --- + dst_device_uuid = json_settings["dst_device_name"] + dst_endpoint_uuid = json_settings["dst_endpoint_name"] + dst_site_location: str = json_settings["dst_site_location"] + dst_ipv4_lan_prefixes: list[LANPrefixesDict] = json_settings[ + "dst_ipv4_lan_prefixes" + ] + dst_site_id: str = json_settings.get("dst_site_id", f"site_{dst_site_location}") + dst_management_type: str = json_settings.get( + "dst_management_type", "ietf-l3vpn-svc:provider-managed" + ) + if dst_management_type != "ietf-l3vpn-svc:provider-managed": + raise Exception("management type %s not supported", dst_management_type) + + dst_role: str = "ietf-l3vpn-svc:spoke-role" + dst_ce_address: str = json_settings["dst_ce_address"] + dst_pe_address: str = json_settings["dst_pe_address"] + dst_ce_pe_network_prefix: int = json_settings["dst_ce_pe_network_prefix"] + dst_mtu: int = json_settings["dst_mtu"] + dst_input_bw: int = json_settings["dst_input_bw"] + dst_output_bw: int = json_settings["dst_output_bw"] + dst_qos_profile_id = "qos-realtime" + dst_qos_profile_direction = "ietf-l3vpn-svc:both" + dst_qos_profile_latency: int = json_settings["dst_qos_profile_latency"] + dst_qos_profile_bw_guarantee: int = json_settings.get( + "dst_qos_profile_bw_guarantee", 100 + ) + + # --- Build site dictionaries using the helper function --- + src_site_dict = create_site_dict( + site_id=src_site_id, + site_location=src_site_location, + device_uuid=src_device_uuid, + endpoint_uuid=src_endpoint_uuid, + service_uuid=service_uuid, + role=src_role, + management_type=src_management_type, + ce_address=src_ce_address, + pe_address=src_pe_address, + ce_pe_network_prefix=src_ce_pe_network_prefix, + mtu=src_mtu, + input_bw=src_input_bw, + output_bw=src_output_bw, + qos_profile_id=src_qos_profile_id, + qos_profile_direction=src_qos_profile_direction, + qos_profile_latency=src_qos_profile_latency, + qos_profile_bw_guarantee=src_qos_profile_bw_guarantee, + lan_prefixes=src_ipv4_lan_prefixes, + ) + + dst_site_dict = create_site_dict( + site_id=dst_site_id, + site_location=dst_site_location, + device_uuid=dst_device_uuid, + endpoint_uuid=dst_endpoint_uuid, + service_uuid=service_uuid, + role=dst_role, + management_type=dst_management_type, + ce_address=dst_ce_address, + pe_address=dst_pe_address, + ce_pe_network_prefix=dst_ce_pe_network_prefix, + mtu=dst_mtu, + input_bw=dst_input_bw, + output_bw=dst_output_bw, + qos_profile_id=dst_qos_profile_id, + qos_profile_direction=dst_qos_profile_direction, + qos_profile_latency=dst_qos_profile_latency, + qos_profile_bw_guarantee=dst_qos_profile_bw_guarantee, + lan_prefixes=dst_ipv4_lan_prefixes, + ) + + # --- Combine both sites into one structure --- + sites = { + "site": [ + src_site_dict, + dst_site_dict, + ] + } + + l3_vpn_data_model = { + "ietf-l3vpn-svc:l3vpn-svc": { + "vpn-services": {"vpn-service": [{"vpn-id": service_uuid}]}, + "sites": sites, + } + } + + json_config_rules = [ + json_config_rule_set( + "/service[{:s}]/IETFL3VPN".format(service_uuid), + l3_vpn_data_model, + ), + json_config_rule_set( + "/service[{:s}]/IETFL3VPN/operation".format(service_uuid), + {"type": operation_type}, + ), + ] + + return json_config_rules + + +def teardown_config_rules(service_uuid: str) -> List[Dict]: + json_config_rules = [ + json_config_rule_delete( + "/service[{:s}]/IETFL3VPN".format(service_uuid), + {"id": service_uuid}, + ), + json_config_rule_delete( + "/service[{:s}]/IETFL3VPN/operation".format(service_uuid), + {}, + ), + ] + return json_config_rules + + +def get_link_ep_device_names( + link: Link, context_client: ContextClient +) -> Tuple[str, str, str, str]: + ep_ids = link.link_endpoint_ids + ep_device_id_1 = ep_ids[0].device_id + ep_uuid_1 = ep_ids[0].endpoint_uuid.uuid + device_obj_1 = context_client.GetDevice(ep_device_id_1) + for d_ep in device_obj_1.device_endpoints: + if d_ep.endpoint_id.endpoint_uuid.uuid == ep_uuid_1: + ep_name_1 = d_ep.name + break + else: + raise Exception("endpoint not found") + device_obj_name_1 = device_obj_1.name + ep_device_id_2 = ep_ids[1].device_id + ep_uuid_2 = ep_ids[1].endpoint_uuid.uuid + device_obj_2 = context_client.GetDevice(ep_device_id_2) + for d_ep in device_obj_2.device_endpoints: + if d_ep.endpoint_id.endpoint_uuid.uuid == ep_uuid_2: + ep_name_2 = d_ep.name + break + else: + raise Exception("endpoint not found") + device_obj_name_2 = device_obj_2.name + return ( + device_obj_name_1, + ep_name_1, + device_obj_1, + device_obj_name_2, + ep_name_2, + device_obj_2, + ) diff --git a/src/service/service/service_handlers/l3nm_ietfl3vpn/L3NM_IETFL3VPN_ServiceHandler.py b/src/service/service/service_handlers/l3nm_ietfl3vpn/L3NM_IETFL3VPN_ServiceHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..aa650c8096b9443b85114166ad1d0bc5b6f2fc55 --- /dev/null +++ b/src/service/service/service_handlers/l3nm_ietfl3vpn/L3NM_IETFL3VPN_ServiceHandler.py @@ -0,0 +1,546 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging +from typing import Any, List, Optional, Tuple, TypedDict, Union + +from deepdiff import DeepDiff + +from common.DeviceTypes import DeviceTypeEnum +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.proto.context_pb2 import ( + ConfigRule, + Device, + DeviceId, + Service, + ServiceConfig, +) +from common.tools.object_factory.Device import json_device_id +from common.type_checkers.Checkers import chk_type +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.service_handler_api.Tools import ( + get_device_endpoint_uuids, + get_endpoint_matching, +) +from service.service.task_scheduler.TaskExecutor import TaskExecutor + +from .ConfigRules import setup_config_rules, teardown_config_rules + +RUNNING_RESOURCE_KEY = "running_ietf_slice" +CANDIDATE_RESOURCE_KEY = "candidate_ietf_slice" +MTU = 1500 + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool("Service", "Handler", labels={"handler": "l3nm_ietf_l3vpn"}) + + +class LANPrefixesDict(TypedDict): + lan: str + lan_tag: str + + +class Ipv4Info(TypedDict): + src_lan: str + dst_lan: str + src_port: str + dst_port: str + vlan: str + + +class QoSInfo(TypedDict): + src_qos_profile_latency: int + src_input_bw: int + src_output_bw: int + dst_qos_profile_latency: int + dst_input_bw: int + dst_output_bw: int + + +def get_custom_config_rule( + service_config: ServiceConfig, resource_key: str +) -> Optional[ConfigRule]: + """ + Return the custom ConfigRule from the ServiceConfig matching the given resource_key, + or None if not found. + """ + for cr in service_config.config_rules: + if ( + cr.WhichOneof("config_rule") == "custom" + and cr.custom.resource_key == resource_key + ): + return cr + return None + + +def load_json_rule_data(service_config: ServiceConfig) -> Tuple[dict, dict]: + """ + Loads the running/candidate JSON data from the service_config for IETF slice data. + Raises an exception if either is missing. + """ + running_cr = get_custom_config_rule(service_config, RUNNING_RESOURCE_KEY) + candidate_cr = get_custom_config_rule(service_config, CANDIDATE_RESOURCE_KEY) + + if not running_cr or not candidate_cr: + raise ValueError("Missing running/candidate IETF slice config rules.") + + running_data = json.loads(running_cr.custom.resource_value) + candidate_data = json.loads(candidate_cr.custom.resource_value) + return running_data, candidate_data + + +def extract_match_criterion_ipv4_info(match_criterion: dict) -> Ipv4Info: + """ + Extracts IPv4 match criteria data (src/dst IP, ports, VLAN) from a match_criterion dict. + """ + src_lan = dst_lan = src_port = dst_port = vlan = "" + for type_value in match_criterion["match-type"]: + value = type_value["value"][0] + if type_value["type"] == "ietf-network-slice-service:source-ip-prefix": + src_lan = value + elif type_value["type"] == "ietf-network-slice-service:destination-ip-prefix": + dst_lan = value + elif type_value["type"] == "ietf-network-slice-service:source-tcp-port": + src_port = value + elif type_value["type"] == "ietf-network-slice-service:destination-tcp-port": + dst_port = value + elif type_value["type"] == "ietf-network-slice-service:vlan": + vlan = value + + return Ipv4Info( + src_lan=src_lan, + dst_lan=dst_lan, + src_port=src_port, + dst_port=dst_port, + vlan=vlan, + ) + + +def extract_qos_info_from_connection_group( + src_sdp_id: str, dst_sdp_id: str, connectivity_constructs: list +) -> QoSInfo: + """ + Given a pair of SDP ids and a list of connectivity constructs, extract QoS info + such as latency and bandwidth (for both directions). + """ + + def _extract_qos_fields(cc: dict) -> Tuple[int, int]: + max_delay = 0 + bandwidth = 0 + metric_bounds = cc["service-slo-sle-policy"]["slo-policy"]["metric-bound"] + for metric_bound in metric_bounds: + if ( + metric_bound["metric-type"] + == "ietf-network-slice-service:one-way-delay-maximum" + and metric_bound["metric-unit"] == "milliseconds" + ): + max_delay = int(metric_bound["bound"]) + elif ( + metric_bound["metric-type"] + == "ietf-network-slice-service:one-way-bandwidth" + and metric_bound["metric-unit"] == "Mbps" + ): + # Convert from Mbps to bps + bandwidth = int(metric_bound["bound"]) * 1000000 + return max_delay, bandwidth + + src_cc = next( + cc + for cc in connectivity_constructs + if cc["p2p-sender-sdp"] == src_sdp_id and cc["p2p-receiver-sdp"] == dst_sdp_id + ) + dst_cc = next( + cc + for cc in connectivity_constructs + if cc["p2p-sender-sdp"] == dst_sdp_id and cc["p2p-receiver-sdp"] == src_sdp_id + ) + src_max_delay, src_bandwidth = _extract_qos_fields(src_cc) + dst_max_delay, dst_bandwidth = _extract_qos_fields(dst_cc) + + return QoSInfo( + src_qos_profile_latency=src_max_delay, + src_input_bw=src_bandwidth, + src_output_bw=dst_bandwidth, + dst_qos_profile_latency=dst_max_delay, + dst_input_bw=dst_bandwidth, + dst_output_bw=src_bandwidth, + ) + + +def get_endpoint_settings(device_obj: Device, endpoint_name: str) -> dict: + """ + Helper to retrieve endpoint settings from a device's config rules given an endpoint name. + Raises an exception if not found. + """ + for rule in device_obj.device_config.config_rules: + if ( + rule.WhichOneof("config_rule") == "custom" + and rule.custom.resource_key == f"/endpoints/endpoint[{endpoint_name}]" + ): + return json.loads(rule.custom.resource_value) + raise ValueError(f"Endpoint settings not found for endpoint {endpoint_name}") + + +class L3NM_IETFL3VPN_ServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service: Service, task_executor: TaskExecutor, **settings + ) -> None: + self.__service = service + self.__task_executor = task_executor + self.__settings_handler = SettingsHandler(service.service_config, **settings) + + def __find_IP_transport_edge_endpoints( + self, endpoints + ) -> Tuple[str, str, str, str, Device]: + """ + Searches for two endpoints whose device controllers are IP_SDN_CONTROLLER. + Returns (src_device_uuid, src_endpoint_uuid, dst_device_uuid, dst_endpoint_uuid, controller_device). + Raises an exception if not found or if the two IP devices differ. + """ + + # Find the first IP transport edge endpoint from the head of endpoints + for ep in endpoints: + device_uuid, endpoint_uuid = get_device_endpoint_uuids(ep) + device_obj = self.__task_executor.get_device( + DeviceId(**json_device_id(device_uuid)) + ) + device_controller = self.__task_executor.get_device_controller(device_obj) + if device_controller.device_type == DeviceTypeEnum.IP_SDN_CONTROLLER.value: + src_device_uuid, src_endpoint_uuid = device_uuid, endpoint_uuid + src_device_controller = device_controller + break + else: + raise Exception("No IP transport edge endpoints found") + + # Find the second IP transport edge endpoint from the tail of endpoints + for ep in reversed(endpoints): + device_uuid, endpoint_uuid = get_device_endpoint_uuids(ep) + device_obj = self.__task_executor.get_device( + DeviceId(**json_device_id(device_uuid)) + ) + device_controller = self.__task_executor.get_device_controller(device_obj) + if device_controller.device_type == DeviceTypeEnum.IP_SDN_CONTROLLER.value: + dst_device_uuid, dst_endpoint_uuid = device_uuid, endpoint_uuid + dst_device_controller = device_controller + break + else: + raise Exception("No IP transport edge endpoints found") + + if src_device_controller != dst_device_controller: + raise Exception("Different Src-Dst devices not supported by now") + + return ( + src_device_uuid, + src_endpoint_uuid, + dst_device_uuid, + dst_endpoint_uuid, + src_device_controller, + ) + + def __build_resource_value_dict( + self, + service_id: str, + src_device_obj: Device, + dst_device_obj: Device, + src_endpoint_name: str, + dst_endpoint_name: str, + qos_info: QoSInfo, + src_endpoint_settings: dict, + dst_endpoint_settings: dict, + src_match_criterion_ipv4_info: Ipv4Info, + dst_match_criterion_ipv4_info: Ipv4Info, + ) -> dict: + """ + Builds the final resource-value dict to be used when calling setup_config_rules(). + """ + # Prepare data for source + src_device_name = src_device_obj.name + src_ce_ip = src_endpoint_settings["address_ip"] + src_ce_prefix = src_endpoint_settings["address_prefix"] + src_lan_prefixes = [ + LANPrefixesDict( + lan=src_match_criterion_ipv4_info["dst_lan"], + lan_tag=src_match_criterion_ipv4_info["vlan"], + ) + ] + + # Prepare data for destination + dst_device_name = dst_device_obj.name + dst_ce_ip = dst_endpoint_settings["address_ip"] + dst_ce_prefix = dst_endpoint_settings["address_prefix"] + dst_lan_prefixes = [ + LANPrefixesDict( + lan=dst_match_criterion_ipv4_info["dst_lan"], + lan_tag=dst_match_criterion_ipv4_info["vlan"], + ) + ] + + return { + "uuid": service_id, + "src_device_name": src_device_name, + "src_endpoint_name": src_endpoint_name, + "src_site_location": src_endpoint_settings["site_location"], + "src_ipv4_lan_prefixes": src_lan_prefixes, + "src_ce_address": src_ce_ip, + "src_pe_address": src_ce_ip, + "src_ce_pe_network_prefix": src_ce_prefix, + "src_mtu": MTU, + "src_qos_profile_latency": qos_info["src_qos_profile_latency"], + "src_input_bw": qos_info["src_input_bw"], + "src_output_bw": qos_info["src_output_bw"], + "dst_device_name": dst_device_name, + "dst_endpoint_name": dst_endpoint_name, + "dst_site_location": dst_endpoint_settings["site_location"], + "dst_ipv4_lan_prefixes": dst_lan_prefixes, + "dst_ce_address": dst_ce_ip, + "dst_pe_address": dst_ce_ip, + "dst_ce_pe_network_prefix": dst_ce_prefix, + "dst_mtu": MTU, + "dst_qos_profile_latency": qos_info["dst_qos_profile_latency"], + "dst_input_bw": qos_info["dst_input_bw"], + "dst_output_bw": qos_info["dst_output_bw"], + } + + @metered_subclass_method(METRICS_POOL) + def SetEndpoint( + self, + endpoints: List[Tuple[str, str, Optional[str]]], + connection_uuid: Optional[str] = None, + ) -> List[Union[bool, Exception]]: + chk_type("endpoints", endpoints, list) + if len(endpoints) < 2: + return [] + + results = [] + service_config = self.__service.service_config + + try: + # Identify IP transport edge endpoints + ( + src_device_uuid, + src_endpoint_uuid, + dst_device_uuid, + dst_endpoint_uuid, + controller, + ) = self.__find_IP_transport_edge_endpoints(endpoints) + + # Retrieve device objects + src_device_obj = self.__task_executor.get_device( + DeviceId(**json_device_id(src_device_uuid)) + ) + src_endpoint_obj = get_endpoint_matching(src_device_obj, src_endpoint_uuid) + + dst_device_obj = self.__task_executor.get_device( + DeviceId(**json_device_id(dst_device_uuid)) + ) + dst_endpoint_obj = get_endpoint_matching(dst_device_obj, dst_endpoint_uuid) + + # Obtain endpoint settings + src_endpoint_settings = get_endpoint_settings( + src_device_obj, src_endpoint_obj.name + ) + dst_endpoint_settings = get_endpoint_settings( + dst_device_obj, dst_endpoint_obj.name + ) + + # Load running & candidate data, compute diff + running_data, candidate_data = load_json_rule_data(service_config) + running_candidate_diff = DeepDiff(running_data, candidate_data) + + # Determine service_id and operation_type + slice_service = candidate_data["network-slice-services"]["slice-service"][0] + service_id = slice_service["id"] + if not running_candidate_diff: + operation_type = "create" + elif "values_changed" in running_candidate_diff: + operation_type = "update" + + # Parse relevant connectivity data + sdps = slice_service["sdps"]["sdp"] + connection_group = slice_service["connection-groups"]["connection-group"][0] + connecitivity_constructs = connection_group["connectivity-construct"] + + # The code below assumes a single connectivity construct or + # that the relevant one is the first in the list: + connecitivity_construct = connecitivity_constructs[0] + src_sdp_idx = connecitivity_construct["p2p-sender-sdp"] + dst_sdp_idx = connecitivity_construct["p2p-receiver-sdp"] + + # QoS + qos_info = extract_qos_info_from_connection_group( + src_sdp_idx, dst_sdp_idx, connecitivity_constructs + ) + + # Retrieve match-criterion info + src_sdp = next(sdp for sdp in sdps if sdp["id"] == src_sdp_idx) + dst_sdp = next(sdp for sdp in sdps if sdp["id"] == dst_sdp_idx) + + src_match_criterion = src_sdp["service-match-criteria"]["match-criterion"][ + 0 + ] + dst_match_criterion = dst_sdp["service-match-criteria"]["match-criterion"][ + 0 + ] + src_match_criterion_ipv4_info = extract_match_criterion_ipv4_info( + src_match_criterion + ) + dst_match_criterion_ipv4_info = extract_match_criterion_ipv4_info( + dst_match_criterion + ) + + # Build resource dict & config rules + resource_value_dict = self.__build_resource_value_dict( + service_id=service_id, + src_device_obj=src_device_obj, + dst_device_obj=dst_device_obj, + src_endpoint_name=src_endpoint_obj.name, + dst_endpoint_name=dst_endpoint_obj.name, + qos_info=qos_info, + src_endpoint_settings=src_endpoint_settings, + dst_endpoint_settings=dst_endpoint_settings, + src_match_criterion_ipv4_info=src_match_criterion_ipv4_info, + dst_match_criterion_ipv4_info=dst_match_criterion_ipv4_info, + ) + json_config_rules = setup_config_rules( + service_id, resource_value_dict, operation_type + ) + + # Configure device + del controller.device_config.config_rules[:] + for jcr in json_config_rules: + controller.device_config.config_rules.append(ConfigRule(**jcr)) + self.__task_executor.configure_device(controller) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception( + "Unable to SetEndpoint for Service({:s})".format(str(service_id)) + ) + results.append(e) + + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteEndpoint( + self, + endpoints: List[Tuple[str, str, Optional[str]]], + connection_uuid: Optional[str] = None, + ) -> List[Union[bool, Exception]]: + chk_type("endpoints", endpoints, list) + if len(endpoints) < 2: + return [] + service_config = self.__service.service_config + ietf_slice_candidate_cr = get_custom_config_rule( + service_config, CANDIDATE_RESOURCE_KEY + ) + candidate_resource_value_dict = json.loads( + ietf_slice_candidate_cr.custom.resource_value + ) + service_id = candidate_resource_value_dict["network-slice-services"][ + "slice-service" + ][0]["id"] + results = [] + try: + src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0]) + src_device = self.__task_executor.get_device( + DeviceId(**json_device_id(src_device_uuid)) + ) + src_controller = self.__task_executor.get_device_controller(src_device) + + dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1]) + dst_device = self.__task_executor.get_device( + DeviceId(**json_device_id(dst_device_uuid)) + ) + dst_controller = self.__task_executor.get_device_controller(dst_device) + if ( + src_controller.device_id.device_uuid.uuid + != dst_controller.device_id.device_uuid.uuid + ): + raise Exception("Different Src-Dst devices not supported by now") + controller = src_controller + json_config_rules = teardown_config_rules(service_id) + del controller.device_config.config_rules[:] + for jcr in json_config_rules: + controller.device_config.config_rules.append(ConfigRule(**jcr)) + self.__task_executor.configure_device(controller) + results.append(True) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception( + "Unable to DeleteEndpoint for Service({:s})".format(str(service_id)) + ) + results.append(e) + + return results + + @metered_subclass_method(METRICS_POOL) + def SetConstraint( + self, constraints: List[Tuple[str, Any]] + ) -> List[Union[bool, Exception]]: + chk_type("constraints", constraints, list) + if len(constraints) == 0: + return [] + + msg = "[SetConstraint] Method not implemented. Constraints({:s}) are being ignored." + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConstraint( + self, constraints: List[Tuple[str, Any]] + ) -> List[Union[bool, Exception]]: + chk_type("constraints", constraints, list) + if len(constraints) == 0: + return [] + + msg = "[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored." + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @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 = [] + for resource in resources: + try: + resource_value = json.loads(resource[1]) + self.__settings_handler.set(resource[0], resource_value) + results.append(True) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception("Unable to SetConfig({:s})".format(str(resource))) + results.append(e) + + 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 = [] + for resource in resources: + try: + self.__settings_handler.delete(resource[0]) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception("Unable to DeleteConfig({:s})".format(str(resource))) + results.append(e) + + return results diff --git a/src/service/service/service_handlers/l3nm_ietfl3vpn/__init__.py b/src/service/service/service_handlers/l3nm_ietfl3vpn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..906dd19f3c948b03263251f60addb49e2fb522dc --- /dev/null +++ b/src/service/service/service_handlers/l3nm_ietfl3vpn/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2022-2025 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/src/service/service/service_handlers/l3nm_nce/ConfigRules.py b/src/service/service/service_handlers/l3nm_nce/ConfigRules.py new file mode 100644 index 0000000000000000000000000000000000000000..0544d897606afe950725349bfeb68c365189aa21 --- /dev/null +++ b/src/service/service/service_handlers/l3nm_nce/ConfigRules.py @@ -0,0 +1,120 @@ +# 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. + +from typing import Dict, List + +from common.tools.object_factory.ConfigRule import ( + json_config_rule_delete, + json_config_rule_set, +) + + +def setup_config_rules(service_uuid: str, json_settings: Dict) -> List[Dict]: + operation_type: str = json_settings["operation_type"] + app_flow_id: str = json_settings["app_flow_id"] + app_flow_user_id: str = json_settings["app_flow_user_id"] + max_latency: int = json_settings["max_latency"] + max_jitter: int = json_settings["max_jitter"] + max_loss: float = json_settings["max_loss"] + upstream_assure_bw: str = json_settings["upstream_assure_bw"] + upstream_max_bw: str = json_settings["upstream_max_bw"] + downstream_assure_bw: str = json_settings["downstream_assure_bw"] + downstream_max_bw: str = json_settings["downstream_max_bw"] + src_ip: str = json_settings["src_ip"] + src_port: str = json_settings["src_port"] + dst_ip: str = json_settings["dst_ip"] + dst_port: str = json_settings["dst_port"] + + app_flow_app_name: str = f"App_Flow_{app_flow_id}" + app_flow_service_profile: str = f"service_{app_flow_id}" + app_id: str = f"app_{app_flow_id}" + app_feature_id: str = f"feature_{app_flow_id}" + app_flow_name: str = f"App_Flow_{app_flow_id}" + app_flow_max_online_users: int = json_settings.get("app_flow_max_online_users", 1) + app_flow_stas: str = json_settings.get("stas", "00:3D:E1:18:82:9E") + qos_profile_name: str = json_settings.get("app_flow_qos_profile", "AR_VR_Gaming") + app_flow_duration: int = json_settings.get("app_flow_duration", 9999) + protocol: str = json_settings.get("protocol", "tcp") + + app_flow = { + "name": app_flow_name, + "user-id": app_flow_user_id, + "app-name": app_flow_app_name, + "max-online-users": app_flow_max_online_users, + "stas": app_flow_stas, + "qos-profile": qos_profile_name, + "service-profile": app_flow_service_profile, + "duration": app_flow_duration, + } + qos_profile = { + "name": qos_profile_name, + "max-latency": max_latency, + "max-jitter": max_jitter, + "max-loss": max_loss, + "upstream": { + "assure-bandwidth": upstream_assure_bw, + "max-bandwidth": upstream_max_bw, + }, + "downstream": { + "assure-bandwidth": downstream_assure_bw, + "max-bandwidth": downstream_max_bw, + }, + } + application = { + "name": app_flow_app_name, + "app-id": app_id, + "app-features": { + "app-feature": [ + { + "id": app_feature_id, + "dest-ip": dst_ip, + "dest-port": dst_port, + "src-ip": src_ip, + "src-port": src_port, + "protocol": protocol, + } + ] + }, + } + app_flow_datamodel = { + "huawei-nce-app-flow:app-flows": { + "app-flow": [app_flow], + "qos-profiles": {"qos-profile": [qos_profile]}, + "applications": {"application": [application]}, + } + } + json_config_rules = [ + json_config_rule_set( + "/service[{:s}]/AppFlow".format(service_uuid), app_flow_datamodel + ), + json_config_rule_set( + "/service[{:s}]/AppFlow/operation".format(service_uuid), + {"type": operation_type}, + ), + ] + return json_config_rules + + +def teardown_config_rules(service_uuid: str, json_settings: Dict) -> List[Dict]: + json_config_rules = [ + json_config_rule_delete( + "/service[{:s}]/AppFlow".format(service_uuid), + {}, + ), + json_config_rule_delete( + "/service[{:s}]/AppFlow/operation".format(service_uuid), + {}, + ), + ] + return json_config_rules diff --git a/src/service/service/service_handlers/l3nm_nce/L3NMNCEServiceHandler.py b/src/service/service/service_handlers/l3nm_nce/L3NMNCEServiceHandler.py new file mode 100644 index 0000000000000000000000000000000000000000..1317bd0615e4789d7ba76e8c0c6b0923d8f2dec7 --- /dev/null +++ b/src/service/service/service_handlers/l3nm_nce/L3NMNCEServiceHandler.py @@ -0,0 +1,564 @@ +# 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 re +from typing import Any, List, Optional, Tuple, Union, TypedDict, Dict +from uuid import uuid4 + +from deepdiff import DeepDiff + +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.proto.context_pb2 import ConfigRule, DeviceId, Empty, Service, ServiceConfig +from common.tools.object_factory.Device import json_device_id +from common.type_checkers.Checkers import chk_type +from context.client.ContextClient import ContextClient +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.service_handler_api.Tools import ( + get_device_endpoint_uuids, +) +from service.service.task_scheduler.TaskExecutor import TaskExecutor + +from .ConfigRules import setup_config_rules, teardown_config_rules + +RUNNING_RESOURCE_KEY = "running_ietf_slice" +CANDIDATE_RESOURCE_KEY = "candidate_ietf_slice" + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool("Service", "Handler", labels={"handler": "l3nm_nce"}) + +SDP_DIFF_RE = re.compile( + r"^root\[\'network-slice-services\'\]\[\'slice-service\'\]\[0\]\[\'sdps\'\]\[\'sdp\'\]\[(\d)\]$" +) +CONNECTION_GROUP_DIFF_RE = re.compile( + r"^root\[\'network-slice-services\'\]\[\'slice-service\'\]\[0\]\[\'connection-groups\'\]\[\'connection-group\'\]\[(\d)\]$" +) +MATCH_CRITERION_DIFF_RE = re.compile( + r"^root\[\'network-slice-services\'\]\[\'slice-service\'\]\[0\]\[\'sdps\'\]\[\'sdp\'\]\[(\d)\]\[\'service-match-criteria\'\]\[\'match-criterion\'\]\[(\d)\]$" +) + + +class Ipv4Info(TypedDict): + src_ip: str + dst_ip: str + src_port: str + dst_port: str + + +def get_removed_items( + candidate_ietf_slice_dict: dict, running_ietf_slice_dict: dict +) -> dict: + """ + For the 'iterable_item_removed' scenario, returns dict with removed sdp / connection_group / match_criterion info. + Raises an exception if there's inconsistent data or multiple items removed (which is not supported). + """ + removed_items = { + "sdp": {"sdp_idx": None, "value": {}}, + "connection_group": {"connection_group_idx": None, "value": {}}, + "match_criterion": { + "sdp_idx": None, + "match_criterion_idx": None, + "value": {}, + }, + } + + running_slice_services = running_ietf_slice_dict["network-slice-services"][ + "slice-service" + ][0] + candidate_slice_services = candidate_ietf_slice_dict["network-slice-services"][ + "slice-service" + ][0] + + running_slice_sdps = [sdp["id"] for sdp in running_slice_services["sdps"]["sdp"]] + candidiate_slice_sdps = [ + sdp["id"] for sdp in candidate_slice_services["sdps"]["sdp"] + ] + removed_sdps = set(running_slice_sdps) - set(candidiate_slice_sdps) + + if len(removed_sdps) > 1: + raise Exception("Multiple SDPs removed - not supported.") + removed_sdp_id = removed_sdps.pop() + + removed_items["sdp"]["sdp_idx"] = running_slice_sdps.index(removed_sdp_id) + removed_items["sdp"]["value"] = next( + sdp + for sdp in running_slice_services["sdps"]["sdp"] + if sdp["id"] == removed_sdp_id + ) + + match_criteria = removed_items["sdp"]["value"]["service-match-criteria"][ + "match-criterion" + ] + if len(match_criteria) > 1: + raise Exception("Multiple match criteria found - not supported") + match_criterion = match_criteria[0] + connection_grp_id = match_criterion["target-connection-group-id"] + connection_groups = running_slice_services["connection-groups"]["connection-group"] + connection_group = next( + (idx, cg) + for idx, cg in enumerate(connection_groups) + if cg["id"] == connection_grp_id + ) + removed_items["connection_group"]["connection_group_idx"] = connection_group[0] + removed_items["connection_group"]["value"] = connection_group[1] + + for sdp in running_slice_services["sdps"]["sdp"]: + if sdp["id"] == removed_sdp_id: + continue + for mc in sdp["service-match-criteria"]["match-criterion"]: + if mc["target-connection-group-id"] == connection_grp_id: + removed_items["match_criterion"]["sdp_idx"] = running_slice_sdps.index( + sdp["id"] + ) + removed_items["match_criterion"]["match_criterion_idx"] = sdp[ + "service-match-criteria" + ]["match-criterion"].index(mc) + removed_items["match_criterion"]["value"] = mc + break + + if ( + removed_items["match_criterion"]["sdp_idx"] is None + or removed_items["sdp"]["sdp_idx"] is None + or removed_items["connection_group"]["connection_group_idx"] is None + ): + raise Exception("sdp, connection group or match criterion not found") + + return removed_items + + +def get_custom_config_rule( + service_config: ServiceConfig, resource_key: str +) -> Optional[ConfigRule]: + """ + Returns the ConfigRule from service_config matching the provided resource_key + if found, otherwise returns None. + """ + for cr in service_config.config_rules: + if ( + cr.WhichOneof("config_rule") == "custom" + and cr.custom.resource_key == resource_key + ): + return cr + return None + + +def get_running_candidate_ietf_slice_data_diff(service_config: ServiceConfig) -> Dict: + """ + Loads the JSON from the running/candidate resource ConfigRules and returns + their DeepDiff comparison. + """ + running_cr = get_custom_config_rule(service_config, RUNNING_RESOURCE_KEY) + candidate_cr = get_custom_config_rule(service_config, CANDIDATE_RESOURCE_KEY) + + running_value_dict = json.loads(running_cr.custom.resource_value) + candidate_value_dict = json.loads(candidate_cr.custom.resource_value) + + return DeepDiff(running_value_dict, candidate_value_dict) + + +def extract_qos_info( + connection_groups: List, connection_grp_id: str, src_sdp_idx: str, dst_sdp_idx: str +) -> Dict: + """ + Extract QoS information from connection groups based on the connection group ID. + """ + qos_info = { + "upstream": {"max_delay": "0", "bw": "0", "packet_loss": "0"}, + "downstream": {"max_delay": "0", "bw": "0", "packet_loss": "0"}, + } + connection_group = next( + (cg for cg in connection_groups if cg["id"] == connection_grp_id), None + ) + + if not connection_group: + return qos_info + + for cc in connection_group["connectivity-construct"]: + if ( + cc["p2p-sender-sdp"] == src_sdp_idx + and cc["p2p-receiver-sdp"] == dst_sdp_idx + ): + direction = "upstream" + elif ( + cc["p2p-sender-sdp"] == dst_sdp_idx + and cc["p2p-receiver-sdp"] == src_sdp_idx + ): + direction = "downstream" + else: + raise Exception("invalid sender and receiver sdp ids") + for metric_bound in cc["service-slo-sle-policy"]["slo-policy"]["metric-bound"]: + if ( + metric_bound["metric-type"] + == "ietf-network-slice-service:one-way-delay-maximum" + and metric_bound["metric-unit"] == "milliseconds" + ): + qos_info[direction]["max_delay"] = metric_bound["bound"] + elif ( + metric_bound["metric-type"] + == "ietf-network-slice-service:one-way-bandwidth" + and metric_bound["metric-unit"] == "Mbps" + ): + qos_info[direction]["bw"] = metric_bound["bound"] + elif ( + metric_bound["metric-type"] + == "ietf-network-slice-service:two-way-packet-loss" + and metric_bound["metric-unit"] == "percentage" + ): + qos_info[direction]["packet_loss"] = metric_bound["percentile-value"] + + return qos_info + + +def extract_match_criterion_ipv4_info(match_criterion: Dict) -> Ipv4Info: + """ + Extracts IPv4 info from the match criterion dictionary. + """ + src_ip = dst_ip = src_port = dst_port = "" + + for type_value in match_criterion["match-type"]: + m_type = type_value["type"] + val = type_value["value"][0] + if m_type == "ietf-network-slice-service:source-ip-prefix": + src_ip = val.split("/")[0] + elif m_type == "ietf-network-slice-service:destination-ip-prefix": + dst_ip = val.split("/")[0] + elif m_type == "ietf-network-slice-service:source-tcp-port": + src_port = val + elif m_type == "ietf-network-slice-service:destination-tcp-port": + dst_port = val + + return Ipv4Info( + src_ip=src_ip, + dst_ip=dst_ip, + src_port=src_port, + dst_port=dst_port, + ) + + +class L3NMNCEServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service: Service, task_executor: TaskExecutor, **settings + ) -> None: + self.__service = service + self.__task_executor = task_executor + self.__settings_handler = SettingsHandler(service.service_config, **settings) + + @metered_subclass_method(METRICS_POOL) + def SetEndpoint( + self, + endpoints: List[Tuple[str, str, Optional[str]]], + connection_uuid: Optional[str] = None, + ) -> List[Union[bool, Exception]]: + chk_type("endpoints", endpoints, list) + if len(endpoints) == 0: + return [] + + results = [] + try: + context_client = ContextClient() + service_config = self.__service.service_config + settings = self.__settings_handler.get("/settings") + + src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0]) + src_device_obj = self.__task_executor.get_device( + DeviceId(**json_device_id(src_device_uuid)) + ) + controller = self.__task_executor.get_device_controller(src_device_obj) + + list_devices = context_client.ListDevices(Empty()) + devices = list_devices.devices + device_name_map = {d.name: d for d in devices} + + running_candidate_diff = get_running_candidate_ietf_slice_data_diff( + service_config + ) + candidate_ietf_slice_cr = get_custom_config_rule( + service_config, CANDIDATE_RESOURCE_KEY + ) + candidate_resource_value_dict = json.loads( + candidate_ietf_slice_cr.custom.resource_value + ) + running_ietf_slice_cr = get_custom_config_rule( + service_config, RUNNING_RESOURCE_KEY + ) + running_resource_value_dict = json.loads( + running_ietf_slice_cr.custom.resource_value + ) + + service_name = running_resource_value_dict["network-slice-services"][ + "slice-service" + ][0]["id"] + + if not running_candidate_diff: # Slice Creation + operation_type = "create" + + slice_service = candidate_resource_value_dict["network-slice-services"][ + "slice-service" + ][0] + sdps = slice_service["sdps"]["sdp"] + connection_groups = slice_service["connection-groups"][ + "connection-group" + ] + sdp_ids = [sdp["id"] for sdp in sdps] + for sdp in sdps: + node_id = sdp["node-id"] + device_obj = device_name_map[node_id] + device_controller = self.__task_executor.get_device_controller( + device_obj + ) + if ( + device_controller is None + or controller.name != device_controller.name + ): + continue + src_sdp_idx = sdp_ids.pop(sdp_ids.index(sdp["id"])) + dst_sdp_idx = sdp_ids[0] + match_criteria = sdp["service-match-criteria"]["match-criterion"] + match_criterion = match_criteria[0] + connection_grp_id = match_criterion["target-connection-group-id"] + break + else: + raise Exception("connection group id not found") + elif "iterable_item_added" in running_candidate_diff: # new SDP added + operation_type = "create" + + slice_service = candidate_resource_value_dict["network-slice-services"][ + "slice-service" + ][0] + sdps = slice_service["sdps"]["sdp"] + connection_groups = slice_service["connection-groups"][ + "connection-group" + ] + added_items = { + "sdp": {"sdp_idx": None, "value": {}}, + "connection_group": {"connection_group_idx": None, "value": {}}, + "match_criterion": { + "sdp_idx": None, + "match_criterion_idx": None, + "value": {}, + }, + } + for added_key, added_value in running_candidate_diff[ + "iterable_item_added" + ].items(): + sdp_match = SDP_DIFF_RE.match(added_key) + connection_group_match = CONNECTION_GROUP_DIFF_RE.match(added_key) + match_criterion_match = MATCH_CRITERION_DIFF_RE.match(added_key) + if sdp_match: + added_items["sdp"] = { + "sdp_idx": int(sdp_match.groups()[0]), + "value": added_value, + } + elif connection_group_match: + added_items["connection_group"] = { + "connection_group_idx": int( + connection_group_match.groups()[0] + ), + "value": added_value, + } + elif match_criterion_match: + added_items["match_criterion"] = { + "sdp_idx": int(match_criterion_match.groups()[0]), + "match_criterion_idx": int( + match_criterion_match.groups()[1] + ), + "value": added_value, + } + new_sdp = sdps[added_items["sdp"]["sdp_idx"]] + src_sdp_idx = new_sdp["id"] + dst_sdp_idx = sdps[added_items["match_criterion"]["sdp_idx"]]["id"] + connection_grp_id = connection_groups[ + added_items["connection_group"]["connection_group_idx"] + ]["id"] + + if ( + connection_grp_id + != added_items["match_criterion"]["value"][ + "target-connection-group-id" + ] + ): + raise Exception( + "connection group missmatch in destination sdp and added connection group" + ) + match_criteria = new_sdp["service-match-criteria"]["match-criterion"] + match_criterion = match_criteria[0] + elif "iterable_item_removed" in running_candidate_diff: # new SDP added + operation_type = "delete" + + slice_service = running_resource_value_dict["network-slice-services"][ + "slice-service" + ][0] + sdps = slice_service["sdps"]["sdp"] + connection_groups = slice_service["connection-groups"][ + "connection-group" + ] + removed_items = get_removed_items( + candidate_resource_value_dict, running_resource_value_dict + ) + removed_sdp = sdps[removed_items["sdp"]["sdp_idx"]] + src_sdp_idx = removed_sdp["id"] + dst_sdp_idx = sdps[removed_items["match_criterion"]["sdp_idx"]]["id"] + connection_grp_id = connection_groups[ + removed_items["connection_group"]["connection_group_idx"] + ]["id"] + + if ( + connection_grp_id + != removed_items["match_criterion"]["value"][ + "target-connection-group-id" + ] + ): + raise Exception( + "connection group missmatch in destination sdp and added connection group" + ) + match_criteria = removed_sdp["service-match-criteria"][ + "match-criterion" + ] + match_criterion = match_criteria[0] + else: + raise Exception( + "transition from candidate to running info not supported" + ) + + ip_info = extract_match_criterion_ipv4_info(match_criterion) + + qos_info = extract_qos_info( + connection_groups, connection_grp_id, src_sdp_idx, dst_sdp_idx + ) + + resource_value_dict = { + "uuid": service_name, + "operation_type": operation_type, + "app_flow_id": f"{src_sdp_idx}_{dst_sdp_idx}_{service_name}", + "app_flow_user_id": str(uuid4()), + "max_latency": int(qos_info["upstream"]["max_delay"]), + "max_jitter": 10, + "max_loss": float(qos_info["upstream"]["packet_loss"]), + "upstream_assure_bw": int(qos_info["upstream"]["bw"]) * 1e6, + "upstream_max_bw": 2 * int(qos_info["upstream"]["bw"]) * 1e6, + "downstream_assure_bw": int(qos_info["downstream"]["bw"]) * 1e6, + "downstream_max_bw": 2 * int(qos_info["downstream"]["bw"]) * 1e6, + "src_ip": ip_info["src_ip"], + "src_port": ip_info["src_port"], + "dst_ip": ip_info["dst_ip"], + "dst_port": ip_info["dst_port"], + } + json_config_rules = setup_config_rules(service_name, resource_value_dict) + + del controller.device_config.config_rules[:] + for jcr in json_config_rules: + controller.device_config.config_rules.append(ConfigRule(**jcr)) + + self.__task_executor.configure_device(controller) + LOGGER.debug('Configured device "{:s}"'.format(controller.name)) + + except Exception as e: # pylint: disable=broad-except + results.append(e) + + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteEndpoint( + self, + endpoints: List[Tuple[str, str, Optional[str]]], + connection_uuid: Optional[str] = None, + ) -> List[Union[bool, Exception]]: + chk_type("endpoints", endpoints, list) + if len(endpoints) == 0: + return [] + service_uuid = self.__service.service_id.service_uuid.uuid + results = [] + try: + src_device_uuid, src_endpoint_uuid = get_device_endpoint_uuids(endpoints[0]) + src_device_obj = self.__task_executor.get_device( + DeviceId(**json_device_id(src_device_uuid)) + ) + controller = self.__task_executor.get_device_controller(src_device_obj) + json_config_rules = teardown_config_rules(service_uuid, {}) + if len(json_config_rules) > 0: + del controller.device_config.config_rules[:] + for json_config_rule in json_config_rules: + controller.device_config.config_rules.append( + ConfigRule(**json_config_rule) + ) + self.__task_executor.configure_device(controller) + results.append(True) + except Exception as e: # pylint: disable=broad-except + results.append(e) + return results + + @metered_subclass_method(METRICS_POOL) + def SetConstraint( + self, constraints: List[Tuple[str, Any]] + ) -> List[Union[bool, Exception]]: + chk_type("constraints", constraints, list) + if len(constraints) == 0: + return [] + + msg = "[SetConstraint] Method not implemented. Constraints({:s}) are being ignored." + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConstraint( + self, constraints: List[Tuple[str, Any]] + ) -> List[Union[bool, Exception]]: + chk_type("constraints", constraints, list) + if len(constraints) == 0: + return [] + + msg = "[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored." + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @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 = [] + for resource in resources: + try: + resource_value = json.loads(resource[1]) + self.__settings_handler.set(resource[0], resource_value) + results.append(True) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception("Unable to SetConfig({:s})".format(str(resource))) + results.append(e) + + 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 = [] + for resource in resources: + try: + self.__settings_handler.delete(resource[0]) + except Exception as e: # pylint: disable=broad-except + LOGGER.exception("Unable to DeleteConfig({:s})".format(str(resource))) + results.append(e) + + return results diff --git a/src/service/service/service_handlers/l3nm_nce/__init__.py b/src/service/service/service_handlers/l3nm_nce/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ccc21c7db78aac26daa1f8c5ff8e1ffd3f35460 --- /dev/null +++ b/src/service/service/service_handlers/l3nm_nce/__init__.py @@ -0,0 +1,14 @@ +# 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. + diff --git a/src/telemetry/backend/collector_api/_Collector.py b/src/telemetry/backend/collector_api/_Collector.py new file mode 100644 index 0000000000000000000000000000000000000000..ec4ba943c90de8a8d683d1e7a9dd9d48865b5edf --- /dev/null +++ b/src/telemetry/backend/collector_api/_Collector.py @@ -0,0 +1,236 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 threading +from typing import Any, Iterator, List, Optional, Tuple, Union + +# Special resource names to request to the collector to retrieve the specified +# configuration/structural resources. +# These resource names should be used with GetConfig() method. +RESOURCE_ENDPOINTS = '__endpoints__' +RESOURCE_INTERFACES = '__interfaces__' +RESOURCE_NETWORK_INSTANCES = '__network_instances__' +RESOURCE_ROUTING_POLICIES = '__routing_policies__' +RESOURCE_SERVICES = '__services__' +RESOURCE_ACL = '__acl__' +RESOURCE_INVENTORY = '__inventory__' + + +class _Collector: + def __init__(self, name : str, address: str, port: int, **settings) -> None: + """ Initialize Collector. + Parameters: + address : str + The address of the device + port : int + The port of the device + **settings + Extra settings required by the collector. + """ + self._name = name + self._address = address + self._port = port + self._settings = settings + + @property + def name(self): return self._name + + @property + def address(self): return self._address + + @property + def port(self): return self._port + + @property + def settings(self): return self._settings + + def Connect(self) -> bool: + """ Connect to the Device. + Returns: + succeeded : bool + Boolean variable indicating if connection succeeded. + """ + raise NotImplementedError() + + def Disconnect(self) -> bool: + """ Disconnect from the Device. + Returns: + succeeded : bool + Boolean variable indicating if disconnection succeeded. + """ + raise NotImplementedError() + + def GetInitialConfig(self) -> List[Tuple[str, Any]]: + """ Retrieve initial configuration of entire device. + Returns: + values : List[Tuple[str, Any]] + List of tuples (resource key, resource value) for + resource keys. + """ + raise NotImplementedError() + + def GetConfig(self, resource_keys: List[str] = []) -> \ + List[Tuple[str, Union[Any, None, Exception]]]: + """ Retrieve running configuration of entire device or + selected resource keys. + Parameters: + resource_keys : List[str] + List of keys pointing to the resources to be retrieved. + Returns: + values : List[Tuple[str, Union[Any, None, Exception]]] + List of tuples (resource key, resource value) for + resource keys requested. If a resource is found, + the appropriate value type must be retrieved. + If a resource is not found, None must be retrieved as + value for that resource. In case of Exception, + the Exception must be retrieved as value. + """ + raise NotImplementedError() + + def SetConfig(self, resources: List[Tuple[str, Any]]) -> \ + List[Union[bool, Exception]]: + """ Create/Update configuration for a list of resources. + Parameters: + resources : List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing the + resource to be modified, and a resource_value containing + the new value to be set. + Returns: + results : List[Union[bool, Exception]] + List of results for resource key changes requested. + Return values must be in the same order as the + resource keys requested. If a resource is properly set, + True must be retrieved; otherwise, the Exception that is + raised during the processing must be retrieved. + """ + raise NotImplementedError() + + def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> \ + List[Union[bool, Exception]]: + """ Delete configuration for a list of resources. + Parameters: + resources : List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing the + resource to be modified, and a resource_value containing + possible additionally required values to locate + the value to be removed. + Returns: + results : List[Union[bool, Exception]] + List of results for resource key deletions requested. + Return values must be in the same order as the resource keys + requested. If a resource is properly deleted, True must be + retrieved; otherwise, the Exception that is raised during + the processing must be retrieved. + """ + raise NotImplementedError() + + def SubscribeState(self, subscriptions: List[Tuple[str, float, float]]) -> \ + List[Union[bool, Exception]]: + """ Subscribe to state information of entire device or + selected resources. Subscriptions are incremental. + Collector should keep track of requested resources. + Parameters: + subscriptions : List[Tuple[str, float, float]] + List of tuples, each containing a resource_key pointing the + resource to be subscribed, a sampling_duration, and a + sampling_interval (both in seconds with float + representation) defining, respectively, for how long + monitoring should last, and the desired monitoring interval + for the resource specified. + Returns: + results : List[Union[bool, Exception]] + List of results for resource key subscriptions requested. + Return values must be in the same order as the resource keys + requested. If a resource is properly subscribed, + True must be retrieved; otherwise, the Exception that is + raised during the processing must be retrieved. + """ + raise NotImplementedError() + + def UnsubscribeState(self, subscriptions: List[Tuple[str, float, float]]) \ + -> List[Union[bool, Exception]]: + """ Unsubscribe from state information of entire device + or selected resources. Subscriptions are incremental. + Collector should keep track of requested resources. + Parameters: + subscriptions : List[str] + List of tuples, each containing a resource_key pointing the + resource to be subscribed, a sampling_duration, and a + sampling_interval (both in seconds with float + representation) defining, respectively, for how long + monitoring should last, and the desired monitoring interval + for the resource specified. + Returns: + results : List[Union[bool, Exception]] + List of results for resource key un-subscriptions requested. + Return values must be in the same order as the resource keys + requested. If a resource is properly unsubscribed, + True must be retrieved; otherwise, the Exception that is + raised during the processing must be retrieved. + """ + raise NotImplementedError() + + def GetState( + self, blocking=False, terminate : Optional[threading.Event] = None + ) -> Iterator[Tuple[float, str, Any]]: + """ Retrieve last collected values for subscribed resources. + Operates as a generator, so this method should be called once and will + block until values are available. When values are available, + it should yield each of them and block again until new values are + available. When the collector is destroyed, GetState() can return instead + of yield to terminate the loop. + Terminate enables to request interruption of the generation. + Examples: + # keep looping waiting for extra samples (generator loop) + terminate = threading.Event() + i = 0 + for timestamp,resource_key,resource_value in my_collector.GetState(blocking=True, terminate=terminate): + process(timestamp, resource_key, resource_value) + i += 1 + if i == 10: terminate.set() + + # just retrieve accumulated samples + samples = my_collector.GetState(blocking=False, terminate=terminate) + # or (as classical loop) + i = 0 + for timestamp,resource_key,resource_value in my_collector.GetState(blocking=False, terminate=terminate): + process(timestamp, resource_key, resource_value) + i += 1 + if i == 10: terminate.set() + Parameters: + blocking : bool + Select the collector behaviour. In both cases, the collector will + first retrieve the samples accumulated and available in the + internal queue. Then, if blocking, the collector does not + terminate the loop and waits for additional samples to come, + thus behaving as a generator. If non-blocking, the collector + terminates the loop and returns. Non-blocking behaviour can + be used for periodically polling the collector, while blocking + can be used when a separate thread is in charge of + collecting the samples produced by the collector. + terminate : threading.Event + Signals the interruption of the GetState method as soon as + possible. + Returns: + results : Iterator[Tuple[float, str, Any]] + Sequences of state sample. Each State sample contains a + float Unix-like timestamps of the samples in seconds with up + to microsecond resolution, the resource_key of the sample, + and its resource_value. + Only resources with an active subscription must be + retrieved. Interval and duration of the sampling process are + specified when creating the subscription using method + SubscribeState(). Order of values yielded is arbitrary. + """ + raise NotImplementedError() diff --git a/src/telemetry/backend/collector_api/__init__.py b/src/telemetry/backend/collector_api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..023830645e0fcb60e3f8583674a954810af222f2 --- /dev/null +++ b/src/telemetry/backend/collector_api/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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/telemetry/backend/collectors/__init__.py b/src/telemetry/backend/collectors/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..023830645e0fcb60e3f8583674a954810af222f2 --- /dev/null +++ b/src/telemetry/backend/collectors/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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/telemetry/backend/collectors/emulated/EmulatedCollector.py b/src/telemetry/backend/collectors/emulated/EmulatedCollector.py new file mode 100644 index 0000000000000000000000000000000000000000..90be013368c5aa80dcb52c2394e8b74f9d74b6f4 --- /dev/null +++ b/src/telemetry/backend/collectors/emulated/EmulatedCollector.py @@ -0,0 +1,450 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 pytz +import queue +import logging +import uuid +import json +from anytree import Node, Resolver +from apscheduler.events import EVENT_JOB_ADDED, EVENT_JOB_REMOVED +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.jobstores.memory import MemoryJobStore +from apscheduler.executors.pool import ThreadPoolExecutor +from datetime import datetime, timedelta +from typing import Any, Iterator, List, Tuple, Union, Optional +from telemetry.backend.collector_api._Collector import _Collector +from .EmulatedHelper import EmulatedCollectorHelper +from .SyntheticMetricsGenerator import SyntheticMetricsGenerator + + +class EmulatedCollector(_Collector): + """ + EmulatedCollector is a class that simulates a network collector for testing purposes. + It provides functionalities to manage configurations, state subscriptions, and synthetic data generation. + """ + def __init__(self, address: str, port: int, **settings): + super().__init__('emulated_collector', address, port, **settings) + self._initial_config = Node('root') # Tree structure for initial config + self._running_config = Node('root') # Tree structure for running config + self._subscriptions = Node('subscriptions') # Tree for state subscriptions + self._resolver = Resolver() # For path resolution in tree structures + self._out_samples = queue.Queue() # Queue to hold synthetic state samples + self._synthetic_data = SyntheticMetricsGenerator(metric_queue=self._out_samples) # Placeholder for synthetic data generator + self._scheduler = BackgroundScheduler(daemon=True) + self._scheduler.configure( + jobstores = {'default': MemoryJobStore()}, + executors = {'default': ThreadPoolExecutor(max_workers=1)}, + timezone = pytz.utc + ) + self._scheduler.add_listener(self._listener_job_added_to_subscription_tree, EVENT_JOB_ADDED) + self._scheduler.add_listener(self._listener_job_removed_from_subscription_tree, EVENT_JOB_REMOVED) + self._helper_methods = EmulatedCollectorHelper() + + self.logger = logging.getLogger(__name__) + self.connected = False # To track connection state + self.logger.info("EmulatedCollector initialized") + + def Connect(self) -> bool: + self.logger.info(f"Connecting to {self.address}:{self.port}") + self.connected = True + self._scheduler.start() + self.logger.info(f"Successfully connected to {self.address}:{self.port}") + return True + + def Disconnect(self) -> bool: + self.logger.info(f"Disconnecting from {self.address}:{self.port}") + if not self.connected: + self.logger.warning("Collector is not connected. Nothing to disconnect.") + return False + self._scheduler.shutdown() + self.connected = False + self.logger.info(f"Successfully disconnected from {self.address}:{self.port}") + return True + + def _require_connection(self): + if not self.connected: + raise RuntimeError("Collector is not connected. Please connect before performing operations.") + + def SubscribeState(self, subscriptions: List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + self._require_connection() + results = [] + for resource_key, duration, interval in subscriptions: + resource_key = self._helper_methods.validate_resource_key(resource_key) # Validate the endpoint name + self.logger.info(f"1. Subscribing to {resource_key} with duration {duration}s and interval {interval}s") + try: + self._resolver.get(self._running_config, resource_key) # Verify if the resource key exists in the running configuration + self.logger.info(f"Resource key {resource_key} exists in the configuration.") + resource_value = json.loads(self._resolver.get(self._running_config, resource_key).value) + if resource_value is not None: + sample_type_ids = resource_value['sample_types'] + self.logger.info(f"Sample type IDs for {resource_key}: {sample_type_ids}") + if len(sample_type_ids) == 0: + self.logger.warning(f"No sample types found for {resource_key}. Skipping subscription.") + results.append(False) + continue + else: + self.logger.warning(f"No sample types found for {resource_key}. Skipping subscription.") + results.append(False) + continue + # Add the job to the scheduler + job_id = f"{resource_key}-{uuid.uuid4()}" + self._scheduler.add_job( + self._generate_sample, + 'interval', + seconds=interval, + args=[resource_key, sample_type_ids], + id=job_id, + replace_existing=True, + end_date=datetime.now(pytz.utc) + timedelta(seconds=duration) + ) + self.logger.info(f"Job added to scheduler for resource key {resource_key} with duration {duration}s and interval {interval}s") + results.append(True) + except Exception as e: + self.logger.error(f"Failed to verify resource key or add job: {e}") + results.append(e) + return results + + def UnsubscribeState(self, subscriptions: List[Tuple[str, float, float]]) -> List[Union[bool, Exception]]: + self._require_connection() + results = [] + for resource_key, _, _ in subscriptions: + resource_key = self._helper_methods.validate_resource_key(resource_key) + try: + # Check if job exists + job_ids = [job.id for job in self._scheduler.get_jobs() if resource_key in job.id] + if not job_ids: + self.logger.warning(f"No active jobs found for {resource_key}. It might have already terminated.") + results.append(False) + continue + # Remove jobs + for job_id in job_ids: + self._scheduler.remove_job(job_id) + + self.logger.info(f"Unsubscribed from {resource_key} with job IDs: {job_ids}") + results.append(True) + except Exception as e: + self.logger.exception(f"Failed to unsubscribe from {resource_key}") + results.append(e) + return results + + def GetState(self, blocking: bool = False, terminate: Optional[queue.Queue] = None) -> Iterator[Tuple[float, str, Any]]: + self._require_connection() + start_time = datetime.now(pytz.utc) + duration = 10 # Duration of the subscription in seconds (as an example) + + while True: + try: + if terminate and not terminate.empty(): + self.logger.info("Termination signal received, stopping GetState") + break + + elapsed_time = (datetime.now(pytz.utc) - start_time).total_seconds() + if elapsed_time >= duration: + self.logger.info("Duration expired, stopping GetState") + break + + sample = self._out_samples.get(block=blocking, timeout=1 if blocking else 0.1) + self.logger.info(f"Retrieved state sample: {sample}") + yield sample + except queue.Empty: + if not blocking: + self.logger.info("No more samples in queue, exiting GetState") + return None + + def _generate_sample(self, resource_key: str, sample_type_ids : List[int]): + # Simulate generating a sample for the resource key + self.logger.debug(f"Executing _generate_sample for resource: {resource_key}") + sample = self._synthetic_data.generate_synthetic_data_point(resource_key, sample_type_ids) + self._out_samples.put(sample) + +# ------------- Event Listeners (START)----------------- + + def _listener_job_removed_from_subscription_tree(self, event): + if event.job_id: + # Extract the resource key from the job ID + resource_key = event.job_id.split('-')[0] + resource_key = self._helper_methods.validate_resource_key(resource_key) + + # Remove the subscription from the tree + try: + subscription_path = resource_key.split('/') + parent = self._subscriptions + for part in subscription_path: + parent = next((child for child in parent.children if child.name == part), None) + if not parent: + raise ValueError(f"Subscription path '{resource_key}' not found in tree.") + if parent: + parent.parent.children = tuple(child for child in parent.parent.children if child != parent) + self.logger.warning(f"Automatically removed subscription from subscription_tree for {resource_key} after job termination by listener. Maybe due to timeout.") + except Exception as e: + self.logger.warning(f"Failed to remove subscription for {resource_key}: {e}") + + def _listener_job_added_to_subscription_tree(self, event): + try: + job_id = event.job_id + if job_id: + resource_key = job_id.split('-')[0] # Extract resource key from job ID + resource_key = self._helper_methods.validate_resource_key(resource_key) + subscription_path = resource_key.split('/') + parent = self._subscriptions + for part in subscription_path: + node = next((child for child in parent.children if child.name == part), None) + if not node: + node = Node(part, parent=parent) + parent = node + parent.value = { + "job_id": job_id + } + self.logger.info(f"Automatically added subscription for {resource_key} to the subscription_tree by listener.") + except Exception as e: + self.logger.exception("Failed to add subscription to the tree") + +# ------------- Event Listeners (END)----------------- + +#------------------------------------------------------------------------------------- +# ------- The below methods are kept for debugging purposes (test-case) only --------- +#------------------------------------------------------------------------------------- + +# This method can be commented but this will arise an error in the test-case (@pytest.fixture --> connected_configured_collector()). + def SetConfig(self, resources: dict) -> List[Union[bool, Exception]]: # For debugging purposes. + self._require_connection() + results = [] + + # if not isinstance(resources, dict): + # self.logger.error("Invalid configuration format: resources must be a dictionary.") + # raise ValueError("Invalid configuration format. Must be a dictionary.") + if 'config_rules' not in resources or not isinstance(resources['config_rules'], list): + self.logger.error("Invalid configuration format: 'config_rules' key missing or not a list.") + raise ValueError("Invalid configuration format. Must contain a 'config_rules' key with a list of rules.") + + for rule in resources['config_rules']: + try: + if 'action' not in rule or 'custom' not in rule: + raise ValueError(f"Invalid rule format: {rule}") + + action = rule['action'] + custom = rule['custom'] + resource_key = custom.get('resource_key') + resource_value = custom.get('resource_value') + + if not resource_key: + raise ValueError(f"Resource key is missing in rule: {rule}") + + if resource_value is None: + raise ValueError(f"Resource value is None for key: {resource_key}") + if not resource_key: + raise ValueError(f"Resource key is missing in rule: {rule}") + + if action == 1: # Set action + resource_path = self._helper_methods._parse_resource_key(resource_key) + # self.logger.info(f"1. Setting configuration for resource key {resource_key} and resource_path: {resource_path}") + parent = self._running_config + + for part in resource_path[:-1]: + if '[' in part and ']' in part: + base, index = part.split('[', 1) + index = index.rstrip(']') + parent = self._helper_methods._find_or_create_node(index, self._helper_methods._find_or_create_node(base, parent)) + # self.logger.info(f"2a. Creating node: {base}, {index}, {parent}") + elif resource_path[-1] != 'settings': + # self.logger.info(f"2b. Creating node: {part}") + parent = self._helper_methods._find_or_create_node(part, parent) + + final_part = resource_path[-1] + if final_part in ['address', 'port']: + self._helper_methods._create_or_update_node(final_part, parent, resource_value) + self.logger.info(f"Configured: {resource_key} = {resource_value}") + + if resource_key.startswith("_connect/settings"): + parent = self._helper_methods._find_or_create_node("_connect", self._running_config) + settings_node = self._helper_methods._find_or_create_node("settings", parent) + settings_node.value = None # Ensure settings node has None value + endpoints_node = self._helper_methods._find_or_create_node("endpoints", settings_node) + + for endpoint in resource_value.get("endpoints", []): + uuid = endpoint.get("uuid") + uuid = uuid.replace('/', '_') if uuid else None + if uuid: + # self.logger.info(f"3. Creating endpoint: {uuid}, {endpoint}, {endpoints_node}") + self._helper_methods._create_or_update_node(uuid, endpoints_node, endpoint) + self.logger.info(f"Configured endpoint: {uuid} : {endpoint}") + + elif resource_key.startswith("/interface"): + interface_parent = self._helper_methods._find_or_create_node("interface", self._running_config) + name = resource_value.get("name") + name = name.replace('/', '_') if name else None + if name: + self._helper_methods._create_or_update_node(name, interface_parent, resource_value) + self.logger.info(f"Configured interface: {name} : {resource_value}") + # self.logger.info(f"4. Configured interface: {name}") + + results.append(True) + else: + raise ValueError(f"Unsupported action '{action}' in rule: {rule}") + + if resource_value is None: + raise ValueError(f"Resource value is None for key: {resource_key}") + + except Exception as e: + self.logger.exception(f"Failed to apply rule: {rule}") + results.append(e) + + return results + +#----------------------------------- +# ------- EXTRA Methods ------------ +#----------------------------------- + + # def log_active_jobs(self): # For debugging purposes. + # """ + # Logs the IDs of all active jobs. + # This method retrieves the list of active jobs from the scheduler and logs their IDs using the logger. + # """ + # self._require_connection() + # jobs = self._scheduler.get_jobs() + # self.logger.info(f"Active jobs: {[job.id for job in jobs]}") + + # def print_config_tree(self): # For debugging purposes. + # """ + # Reads the configuration using GetConfig and prints it as a hierarchical tree structure. + # """ + # self._require_connection() + + # def print_tree(node, indent=""): + # """ + # Recursively prints the configuration tree. + + # Args: + # node (Node): The current node to print. + # indent (str): The current indentation level. + # """ + # if node.name != "root": # Skip the root node's name + # value = getattr(node, "value", None) + # print(f"{indent}- {node.name}: {json.loads(value) if value else ''}") + + # for child in node.children: + # print_tree(child, indent + " ") + + # print("Configuration Tree:") + # print_tree(self._running_config) + + + # def GetInitialConfig(self) -> List[Tuple[str, Any]]: # comment + # self._require_connection() + # results = [] + # for node in self._initial_config.descendants: + # value = getattr(node, "value", None) + # results.append((node.name, json.loads(value) if value else None)) + # self.logger.info("Retrieved initial configurations") + # return results + + # def GetConfig(self, resource_keys: List[str] = []) -> List[Tuple[str, Union[Any, dict, Exception]]]: # comment + # """ + # Retrieves the configuration for the specified resource keys. + # If no keys are provided, returns the full configuration tree. + + # Args: + # resource_keys (List[str]): A list of keys specifying the configuration to retrieve. + + # Returns: + # List[Tuple[str, Union[Any, dict, Exception]]]: A list of tuples with the resource key and its value, + # subtree, or an exception. + # """ + # self._require_connection() + # results = [] + + # try: + # if not resource_keys: + # # If no specific keys are provided, return the full configuration tree + + # full_tree = self._helper_methods._generate_subtree(self._running_config) + # # full_tree = self._generate_subtree(self._running_config) + # return [("full_configuration", full_tree)] + + # for key in resource_keys: + # try: + # # Parse the resource key + # resource_path = self._helper_methods.(key) + # self.logger.info(f"1. Retrieving configuration for resource path : {resource_path}") + + # # Navigate to the node corresponding to the key + # parent = self._running_config + # for part in resource_path: + # parent = self._find_or_raise_node(part, parent) + + # # Check if the node has a value + # value = getattr(parent, "value", None) + # if value: + # # If a value exists, return it + # results.append((key, json.loads(value))) + # else: + # # If no value, return the subtree of this node + # subtree = self._helper_methods._generate_subtree(parent) + # # subtree = self._generate_subtree(parent) + # results.append((key, subtree)) + + # except Exception as e: + # self.logger.exception(f"Failed to retrieve configuration for key: {key}") + # results.append((key, e)) + + # except Exception as e: + # self.logger.exception("Failed to retrieve configurations") + # results.append(("Error", e)) + + # return results + + # def DeleteConfig(self, resources: List[Tuple[str, Any]]) -> List[Union[bool, Exception]]: # comment + # self._require_connection() + # results = [] + + # for key in resources: + # try: + # # Parse resource key into parts, handling brackets correctly + # resource_path = self._helper_methods.(key) + + # parent = self._running_config + # for part in resource_path: + # parent = self._find_or_raise_node(part, parent) + + # # Delete the final node + # node_to_delete = parent + # parent = node_to_delete.parent + # parent.children = tuple(child for child in parent.children if child != node_to_delete) + # self.logger.info(f"Deleted configuration for key: {key}") + + # # Handle endpoints structure + # if "interface" in key and "settings" in key: + # interface_name = key.split('[')[-1].split(']')[0] + # endpoints_parent = self._find_or_raise_node("_connect", self._running_config) + # endpoints_node = self._find_or_raise_node("endpoints", endpoints_parent) + # endpoint_to_delete = next((child for child in endpoints_node.children if child.name == interface_name), None) + # if endpoint_to_delete: + # endpoints_node.children = tuple(child for child in endpoints_node.children if child != endpoint_to_delete) + # self.logger.info(f"Removed endpoint entry for interface '{interface_name}'") + + # # Check if parent has no more children and is not the root + # while parent and parent.name != "root" and not parent.children: + # node_to_delete = parent + # parent = node_to_delete.parent + # parent.children = tuple(child for child in parent.children if child != node_to_delete) + # self.logger.info(f"Deleted empty parent node: {node_to_delete.name}") + + # results.append(True) + # except Exception as e: + # self.logger.exception(f"Failed to delete configuration for key: {key}") + # results.append(e) + + # return results + diff --git a/src/telemetry/backend/collectors/emulated/EmulatedHelper.py b/src/telemetry/backend/collectors/emulated/EmulatedHelper.py new file mode 100644 index 0000000000000000000000000000000000000000..ebfb7d49fdb7ceafa00986e763e56dd59e445609 --- /dev/null +++ b/src/telemetry/backend/collectors/emulated/EmulatedHelper.py @@ -0,0 +1,166 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 anytree import Node +import json +from typing import Any, List + + +class EmulatedCollectorHelper: + """ + Helper class for the emulated collector. + """ + def __init__(self): + pass + + def validate_resource_key(self, key: str) -> str: + """ + Splits the input string into two parts: + - The first part is '_connect/settings/endpoints/'. + - The second part is the remaining string after the first part, with '/' replaced by '_'. + + Args: + key (str): The input string to process. + + Returns: + str: A single string with the processed result. + """ + prefix = '_connect/settings/endpoints/' + if not key.startswith(prefix): + raise ValueError(f"The input path '{key}' does not start with the expected prefix: {prefix}") + second_part = key[len(prefix):] + second_part_processed = second_part.replace('/', '_') + validated_key = prefix + second_part_processed + return validated_key + +#-------------------------------------------------------------------------------------- +# ------- Below function is kept for debugging purposes (test-cases) only ------------- +#-------------------------------------------------------------------------------------- + +# This below methods can be commented but are called by the SetConfig method in EmulatedCollector.py + + def _find_or_create_node(self, name: str, parent: Node) -> Node: + """ + Finds or creates a node with the given name under the specified parent. + + Args: + name (str): The name of the node to find or create. + parent (Node): The parent node. + + Returns: + Node: The found or created node. + """ + node = next((child for child in parent.children if child.name == name), None) + if not node: + node = Node(name, parent=parent) + return node + + + def _create_or_update_node(self, name: str, parent: Node, value: Any): + """ + Creates or updates a node with the given name and value under the specified parent. + + Args: + name (str): The name of the node. + parent (Node): The parent node. + value (Any): The value to set on the node. + """ + node = next((child for child in parent.children if child.name == name), None) + if node: + node.value = json.dumps(value) + else: + Node(name, parent=parent, value=json.dumps(value)) + + + def _parse_resource_key(self, resource_key: str) -> List[str]: + """ + Parses the resource key into parts, correctly handling brackets. + + Args: + resource_key (str): The resource key to parse. + + Returns: + List[str]: A list of parts from the resource key. + """ + resource_path = [] + current_part = "" + in_brackets = False + + if not resource_key.startswith('/interface'): + for char in resource_key.strip('/'): + if char == '[': + in_brackets = True + current_part += char + elif char == ']': + in_brackets = False + current_part += char + elif char == '/' and not in_brackets: + resource_path.append(current_part) + current_part = "" + else: + current_part += char + if current_part: + resource_path.append(current_part) + return resource_path + else: + resource_path = resource_key.strip('/').split('/', 1) + if resource_path[1] == 'settings': + return resource_path + else: + resource_path = [resource_key.strip('/').split('[')[0].strip('/'), resource_key.strip('/').split('[')[1].split(']')[0].replace('/', '_')] + return resource_path + + +#----------------------------------- +# ------- EXTRA Methods ------------ +#----------------------------------- + + # def _generate_subtree(self, node: Node) -> dict: + # """ + # Generates a subtree of the configuration tree starting from the specified node. + + # Args: + # node (Node): The node from which to generate the subtree. + + # Returns: + # dict: The subtree as a dictionary. + # """ + # subtree = {} + # for child in node.children: + # if child.children: + # subtree[child.name] = self._generate_subtree(child) + # else: + # value = getattr(child, "value", None) + # subtree[child.name] = json.loads(value) if value else None + # return subtree + + + # def _find_or_raise_node(self, name: str, parent: Node) -> Node: + # """ + # Finds a node with the given name under the specified parent or raises an exception if not found. + + # Args: + # name (str): The name of the node to find. + # parent (Node): The parent node. + + # Returns: + # Node: The found node. + + # Raises: + # ValueError: If the node is not found. + # """ + # node = next((child for child in parent.children if child.name == name), None) + # if not node: + # raise ValueError(f"Node '{name}' not found under parent '{parent.name}'.") + # return node diff --git a/src/telemetry/backend/collectors/emulated/SyntheticMetricsGenerator.py b/src/telemetry/backend/collectors/emulated/SyntheticMetricsGenerator.py new file mode 100644 index 0000000000000000000000000000000000000000..a01e2c0e659f1eea6383030daeafef11c83d7a45 --- /dev/null +++ b/src/telemetry/backend/collectors/emulated/SyntheticMetricsGenerator.py @@ -0,0 +1,129 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 numpy as np +import random +import logging +import queue +import time + +LOGGER = logging.getLogger(__name__) + +class SyntheticMetricsGenerator(): + """ + This collector class generates synthetic network metrics based on the current network state. + The metrics include packet_in, packet_out, bytes_in, bytes_out, packet_loss (percentage), packet_drop_count, byte_drop_count, and latency. + The network state can be 'good', 'moderate', or 'poor', and it affects the generated metrics accordingly. + """ + def __init__(self, metric_queue=None, network_state="good"): + LOGGER.info("Initiaitng Emulator") + super().__init__() + self.metric_queue = metric_queue if metric_queue is not None else queue.Queue() + self.network_state = network_state + self.running = True + self.set_initial_parameter_values() # update this method to set the initial values for the parameters + + def set_initial_parameter_values(self): + self.bytes_per_pkt = random.uniform(65, 150) + self.states = ["good", "moderate", "poor"] + self.state_probabilities = { + "good" : [0.9, 0.1, 0.0], + "moderate": [0.2, 0.7, 0.1], + "poor" : [0.0, 0.3, 0.7] + } + if self.network_state == "good": + self.packet_in = random.uniform(700, 900) + elif self.network_state == "moderate": + self.packet_in = random.uniform(300, 700) + else: + self.packet_in = random.uniform(100, 300) + + def generate_synthetic_data_point(self, resource_key, sample_type_ids): + """ + Generates a synthetic data point based on the current network state. + + Parameters: + resource_key (str): The key associated with the resource for which the data point is generated. + + Returns: + tuple: A tuple containing the timestamp, resource key, and a dictionary of generated metrics. + """ + if self.network_state == "good": + packet_loss = random.uniform(0.01, 0.1) + random_noise = random.uniform(1,10) + latency = random.uniform(5, 25) + elif self.network_state == "moderate": + packet_loss = random.uniform(0.1, 1) + random_noise = random.uniform(10, 40) + latency = random.uniform(25, 100) + elif self.network_state == "poor": + packet_loss = random.uniform(1, 3) + random_noise = random.uniform(40, 100) + latency = random.uniform(100, 300) + else: + raise ValueError("Invalid network state. Must be 'good', 'moderate', or 'poor'.") + + period = 60 * 60 * random.uniform(10, 100) + amplitude = random.uniform(50, 100) + sin_wave = amplitude * np.sin(2 * np.pi * 100 / period) + self.packet_in + packet_in = sin_wave + ((sin_wave/100) * random_noise) + packet_out = packet_in - ((packet_in / 100) * packet_loss) + bytes_in = packet_in * self.bytes_per_pkt + bytes_out = packet_out * self.bytes_per_pkt + packet_drop_count = packet_in * (packet_loss / 100) + byte_drop_count = packet_drop_count * self.bytes_per_pkt + + state_prob = self.state_probabilities[self.network_state] + self.network_state = random.choices(self.states, state_prob)[0] + print (self.network_state) + + generated_samples = { + "packet_in" : int(packet_in), "packet_out" : int(packet_out), "bytes_in" : float(bytes_in), + "bytes_out" : float(bytes_out), "packet_loss": float(packet_loss), "packet_drop_count" : int(packet_drop_count), + "latency" : float(latency), "byte_drop_count": float(byte_drop_count) + } + requested_metrics = self.metric_id_mapper(sample_type_ids, generated_samples) + # generated_samples = {metric: generated_samples[metric] for metric in requested_metrics} + + return (time.time(), resource_key, requested_metrics) + + def metric_id_mapper(self, sample_type_ids, metric_dict): + """ + Maps the sample type IDs to the corresponding metric names. + + Parameters: + sample_type_ids (list): A list of sample type IDs. + + Returns: + list: A list of metric names. + """ + metric_names = [] + for sample_type_id in sample_type_ids: + if sample_type_id == 102: + metric_names.append(metric_dict["packet_in"]) + elif sample_type_id == 101: + metric_names.append(metric_dict["packet_out"]) + elif sample_type_id == 103: + metric_names.append(metric_dict["packet_drop_count"]) + elif sample_type_id == 202: + metric_names.append(metric_dict["bytes_in"]) + elif sample_type_id == 201: + metric_names.append(metric_dict["bytes_out"]) + elif sample_type_id == 203: + metric_names.append(metric_dict["byte_drop_count"]) + elif sample_type_id == 701: + metric_names.append(metric_dict["latency"]) + else: + raise ValueError(f"Invalid sample type ID: {sample_type_id}") + return metric_names diff --git a/src/telemetry/backend/collectors/emulated/__init__.py b/src/telemetry/backend/collectors/emulated/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..023830645e0fcb60e3f8583674a954810af222f2 --- /dev/null +++ b/src/telemetry/backend/collectors/emulated/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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/telemetry/backend/requirements.in b/src/telemetry/backend/requirements.in index effd1752af0d1a2d00312ff4935676c24964c784..2843bdbf68defcc1a972b49bfa12a8107b696aaa 100644 --- a/src/telemetry/backend/requirements.in +++ b/src/telemetry/backend/requirements.in @@ -12,4 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +anytree==2.8.0 confluent-kafka==2.3.* +numpy==2.0.1 +APScheduler==3.10.1 diff --git a/src/telemetry/backend/service/TelemetryBackendService.py b/src/telemetry/backend/service/TelemetryBackendService.py index fe5792a023a926d41a71b0dc7614d7e2b093507b..a1f17df3cb65a6bd13ffb8e96a6a07b536200825 100755 --- a/src/telemetry/backend/service/TelemetryBackendService.py +++ b/src/telemetry/backend/service/TelemetryBackendService.py @@ -12,21 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +import queue import json import time -import random import logging import threading -from typing import Any, Dict -from datetime import datetime, timezone -# from common.proto.context_pb2 import Empty -from confluent_kafka import Producer as KafkaProducer -from confluent_kafka import Consumer as KafkaConsumer -from confluent_kafka import KafkaError +from typing import Any, Dict +from datetime import datetime, timezone +from confluent_kafka import Producer as KafkaProducer +from confluent_kafka import Consumer as KafkaConsumer +from confluent_kafka import KafkaError from common.Constants import ServiceNameEnum -from common.Settings import get_service_port_grpc -from common.tools.kafka.Variables import KafkaConfig, KafkaTopic -from common.method_wrappers.Decorator import MetricsPool +from common.Settings import get_service_port_grpc +from common.method_wrappers.Decorator import MetricsPool +from common.tools.kafka.Variables import KafkaConfig, KafkaTopic from common.tools.service.GenericGrpcService import GenericGrpcService LOGGER = logging.getLogger(__name__) @@ -35,7 +34,7 @@ METRICS_POOL = MetricsPool('TelemetryBackend', 'backendService') class TelemetryBackendService(GenericGrpcService): """ Class listens for request on Kafka topic, fetches requested metrics from device. - Produces metrics on both RESPONSE and VALUE kafka topics. + Produces metrics on both TELEMETRY_RESPONSE and VALUE kafka topics. """ def __init__(self, cls_name : str = __name__) -> None: LOGGER.info('Init TelemetryBackendService') @@ -45,7 +44,9 @@ class TelemetryBackendService(GenericGrpcService): self.kafka_consumer = KafkaConsumer({'bootstrap.servers' : KafkaConfig.get_kafka_address(), 'group.id' : 'backend', 'auto.offset.reset' : 'latest'}) - self.running_threads = {} + self.running_threads = {} + self.emulatorCollector = None + self.metric_queue = queue.Queue() def install_servicers(self): threading.Thread(target=self.RequestListener).start() @@ -57,7 +58,7 @@ class TelemetryBackendService(GenericGrpcService): LOGGER.info('Telemetry backend request listener is running ...') # print ('Telemetry backend request listener is running ...') consumer = self.kafka_consumer - consumer.subscribe([KafkaTopic.REQUEST.value]) + consumer.subscribe([KafkaTopic.TELEMETRY_REQUEST.value]) while True: receive_msg = consumer.poll(2.0) if receive_msg is None: @@ -66,93 +67,83 @@ class TelemetryBackendService(GenericGrpcService): if receive_msg.error().code() == KafkaError._PARTITION_EOF: continue else: - # print("Consumer error: {}".format(receive_msg.error())) + LOGGER.error("Consumer error: {}".format(receive_msg.error())) break try: collector = json.loads(receive_msg.value().decode('utf-8')) collector_id = receive_msg.key().decode('utf-8') LOGGER.debug('Recevied Collector: {:} - {:}'.format(collector_id, collector)) - # print('Recevied Collector: {:} - {:}'.format(collector_id, collector)) if collector['duration'] == -1 and collector['interval'] == -1: self.TerminateCollectorBackend(collector_id) else: - self.RunInitiateCollectorBackend(collector_id, collector) + threading.Thread(target=self.InitiateCollectorBackend, + args=(collector_id, collector)).start() except Exception as e: - LOGGER.warning("Unable to consumer message from topic: {:}. ERROR: {:}".format(KafkaTopic.REQUEST.value, e)) - # print ("Unable to consumer message from topic: {:}. ERROR: {:}".format(KafkaTopic.REQUEST.value, e)) + LOGGER.warning("Unable to consumer message from topic: {:}. ERROR: {:}".format(KafkaTopic.TELEMETRY_REQUEST.value, e)) - def TerminateCollectorBackend(self, collector_id): - if collector_id in self.running_threads: - thread, stop_event = self.running_threads[collector_id] - stop_event.set() - thread.join() - # print ("Terminating backend (by StopCollector): Collector Id: ", collector_id) - del self.running_threads[collector_id] - self.GenerateCollectorTerminationSignal(collector_id, "-1", -1) # Termination confirmation to frontend. - else: - # print ('Backend collector {:} not found'.format(collector_id)) - LOGGER.warning('Backend collector {:} not found'.format(collector_id)) - - def RunInitiateCollectorBackend(self, collector_id: str, collector: str): - stop_event = threading.Event() - thread = threading.Thread(target=self.InitiateCollectorBackend, - args=(collector_id, collector, stop_event)) - self.running_threads[collector_id] = (thread, stop_event) - thread.start() - - def InitiateCollectorBackend(self, collector_id, collector, stop_event): + def InitiateCollectorBackend(self, collector_id, collector): """ Method receives collector request and initiates collecter backend. """ - # print("Initiating backend for collector: ", collector_id) - LOGGER.info("Initiating backend for collector: {:s}".format(str(collector_id))) - start_time = time.time() - while not stop_event.is_set(): - if int(collector['duration']) != -1 and time.time() - start_time >= collector['duration']: # condition to terminate backend - print("Execuation duration completed: Terminating backend: Collector Id: ", collector_id, " - ", time.time() - start_time) - self.GenerateCollectorTerminationSignal(collector_id, "-1", -1) # Termination confirmation to frontend. - break - self.ExtractKpiValue(collector_id, collector['kpi_id']) - time.sleep(collector['interval']) + LOGGER.info("Initiating backend for collector: (Not Implemented... In progress ) {:s}".format(str(collector_id))) + # start_time = time.time() + # self.emulatorCollector = NetworkMetricsEmulator( + # duration = collector['duration'], + # interval = collector['interval'], + # metric_queue = self.metric_queue + # ) + # self.emulatorCollector.start() + # self.running_threads[collector_id] = self.emulatorCollector - def GenerateCollectorTerminationSignal(self, collector_id: str, kpi_id: str, measured_kpi_value: Any): + # while self.emulatorCollector.is_alive(): + # if not self.metric_queue.empty(): + # metric_value = self.metric_queue.get() + # LOGGER.debug("Metric: {:} - Value : {:}".format(collector['kpi_id'], metric_value)) + # self.GenerateKpiValue(collector_id, collector['kpi_id'] , metric_value) + # time.sleep(1) + # self.TerminateCollectorBackend(collector_id) + + def GenerateKpiValue(self, collector_id: str, kpi_id: str, measured_kpi_value: Any): """ - Method to write kpi Termination signat on RESPONSE Kafka topic + Method to write kpi value on VALUE Kafka topic """ producer = self.kafka_producer kpi_value : Dict = { + "time_stamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "kpi_id" : kpi_id, - "kpi_value" : measured_kpi_value, + "kpi_value" : measured_kpi_value } producer.produce( - KafkaTopic.RESPONSE.value, # TODO: to the topic ... + KafkaTopic.VALUE.value, key = collector_id, value = json.dumps(kpi_value), callback = self.delivery_callback ) producer.flush() - def ExtractKpiValue(self, collector_id: str, kpi_id: str): - """ - Method to extract kpi value. - """ - measured_kpi_value = random.randint(1,100) # TODO: To be extracted from a device - # print ("Measured Kpi value: {:}".format(measured_kpi_value)) - self.GenerateCollectorResponse(collector_id, kpi_id , measured_kpi_value) + def TerminateCollectorBackend(self, collector_id): + LOGGER.debug("Terminating collector backend...") + if collector_id in self.running_threads: + thread = self.running_threads[collector_id] + thread.stop() + del self.running_threads[collector_id] + LOGGER.debug("Collector backend terminated. Collector ID: {:}".format(collector_id)) + self.GenerateCollectorTerminationSignal(collector_id, "-1", -1) # Termination confirmation to frontend. + else: + LOGGER.warning('Backend collector {:} not found'.format(collector_id)) - def GenerateCollectorResponse(self, collector_id: str, kpi_id: str, measured_kpi_value: Any): + def GenerateCollectorTerminationSignal(self, collector_id: str, kpi_id: str, measured_kpi_value: Any): """ - Method to write kpi value on RESPONSE Kafka topic + Method to write kpi Termination signat on TELEMETRY_RESPONSE Kafka topic """ producer = self.kafka_producer kpi_value : Dict = { - "time_stamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "kpi_id" : kpi_id, - "kpi_value" : measured_kpi_value + "kpi_value" : measured_kpi_value, } producer.produce( - KafkaTopic.VALUE.value, # TODO: to the topic ... + KafkaTopic.TELEMETRY_RESPONSE.value, key = collector_id, value = json.dumps(kpi_value), callback = self.delivery_callback @@ -160,14 +151,9 @@ class TelemetryBackendService(GenericGrpcService): producer.flush() def delivery_callback(self, err, msg): - """ - Callback function to handle message delivery status. - Args: err (KafkaError): Kafka error object. - msg (Message): Kafka message object. - """ - if err: - LOGGER.error('Message delivery failed: {:}'.format(err)) + if err: + LOGGER.error('Message delivery failed: {:s}'.format(str(err))) # print(f'Message delivery failed: {err}') - #else: - # LOGGER.debug('Message delivered to topic {:}'.format(msg.topic())) - # # print(f'Message delivered to topic {msg.topic()}') + # else: + # LOGGER.info('Message delivered to topic {:}'.format(msg.topic())) + # print(f'Message delivered to topic {msg.topic()}') diff --git a/src/telemetry/backend/tests/messages.py b/src/telemetry/backend/tests/messages.py index 0dc5506ab42d67d67a88cf8976409472213fe098..f6a2bb247f28d10654746e0c75b6ed1973382e38 100644 --- a/src/telemetry/backend/tests/messages.py +++ b/src/telemetry/backend/tests/messages.py @@ -12,4 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +import uuid +import random +from common.proto import telemetry_frontend_pb2 +# from common.proto.kpi_sample_types_pb2 import KpiSampleType +# from common.proto.kpi_manager_pb2 import KpiId + +def create_collector_request(): + _create_collector_request = telemetry_frontend_pb2.Collector() + _create_collector_request.collector_id.collector_id.uuid = str(uuid.uuid4()) + # _create_collector_request.collector_id.collector_id.uuid = "efef4d95-1cf1-43c4-9742-95c283dddddd" + _create_collector_request.kpi_id.kpi_id.uuid = str(uuid.uuid4()) + # _create_collector_request.kpi_id.kpi_id.uuid = "6e22f180-ba28-4641-b190-2287bf448888" + _create_collector_request.duration_s = float(random.randint(8, 16)) + # _create_collector_request.duration_s = -1 + _create_collector_request.interval_s = float(random.randint(2, 4)) + return _create_collector_request diff --git a/src/telemetry/backend/tests/messages_emulated.py b/src/telemetry/backend/tests/messages_emulated.py new file mode 100644 index 0000000000000000000000000000000000000000..e081fb3febc163d40742d3e690cc0e2b3d4b7d61 --- /dev/null +++ b/src/telemetry/backend/tests/messages_emulated.py @@ -0,0 +1,61 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 +# Configure logging to ensure logs appear on the console +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + + +def create_test_configuration(): + return { + "config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": 8080}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": { + "endpoints": [ + {"uuid": "eth0", "type": "ethernet", "sample_types": [101, 102]}, + {"uuid": "eth1", "type": "ethernet", "sample_types": []}, + {"uuid": "13/1/2", "type": "copper", "sample_types": [101, 102, 201, 202]} + ] + }}}, + {"action": 1, "custom": {"resource_key": "/interface[eth0]/settings", "resource_value": { + "name": "eth0", "enabled": True + }}}, + {"action": 1, "custom": {"resource_key": "/interface[eth1]/settings", "resource_value": { + "name": "eth1", "enabled": False + }}}, + {"action": 1, "custom": {"resource_key": "/interface[13/1/2]/settings", "resource_value": { + "name": "13/1/2", "enabled": True + }}} + ] + } + +def create_specific_config_keys(): + keys_to_return = ["_connect/settings/endpoints/eth1", "/interface/[13/1/2]/settings", "_connect/address"] + return keys_to_return + +def create_config_for_delete(): + keys_to_delete = ["_connect/settings/endpoints/eth0", "/interface/[eth1]", "_connect/port"] + return keys_to_delete + +def create_test_subscriptions(): + return [("_connect/settings/endpoints/eth1", 10, 2), + ("_connect/settings/endpoints/13/1/2", 15, 3), + ("_connect/settings/endpoints/eth0", 8, 2)] + +def create_unscubscribe_subscriptions(): + return [("_connect/settings/endpoints/eth1", 10, 2), + ("_connect/settings/endpoints/13/1/2", 15, 3), + ("_connect/settings/endpoints/eth0", 8, 2)] diff --git a/src/telemetry/backend/tests/test_backend.py b/src/telemetry/backend/tests/test_backend.py index 5307cd9fe51f3bb15f5dd4915bfc601318db9551..e75b33ca58c6bf27c5d2e1c2012dc31de5274ad3 100644 --- a/src/telemetry/backend/tests/test_backend.py +++ b/src/telemetry/backend/tests/test_backend.py @@ -13,10 +13,11 @@ # limitations under the License. import logging -import threading +import time +from typing import Dict from common.tools.kafka.Variables import KafkaTopic from telemetry.backend.service.TelemetryBackendService import TelemetryBackendService - +from .messages import create_collector_request LOGGER = logging.getLogger(__name__) @@ -34,4 +35,19 @@ def test_validate_kafka_topics(): # def test_RunRequestListener(): # LOGGER.info('test_RunRequestListener') # TelemetryBackendServiceObj = TelemetryBackendService() -# threading.Thread(target=TelemetryBackendServiceObj.RequestListener).start() \ No newline at end of file +# threading.Thread(target=TelemetryBackendServiceObj.RequestListener).start() + +def test_RunInitiateCollectorBackend(): + LOGGER.debug(">>> RunInitiateCollectorBackend <<<") + collector_obj = create_collector_request() + collector_id = collector_obj.collector_id.collector_id.uuid + collector_dict : Dict = { + "kpi_id" : collector_obj.kpi_id.kpi_id.uuid, + "duration": collector_obj.duration_s, + "interval": collector_obj.interval_s + } + TeleObj = TelemetryBackendService() + TeleObj.InitiateCollectorBackend(collector_id, collector_dict) + time.sleep(20) + + LOGGER.debug("--- Execution Finished Sucessfully---") diff --git a/src/telemetry/backend/tests/test_emulated.py b/src/telemetry/backend/tests/test_emulated.py new file mode 100644 index 0000000000000000000000000000000000000000..feb5b1f7f92de4016f3bcb8eff8e17b843bf0c3e --- /dev/null +++ b/src/telemetry/backend/tests/test_emulated.py @@ -0,0 +1,108 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 +import time +import pytest +from telemetry.backend.collectors.emulated.EmulatedCollector import EmulatedCollector +from telemetry.backend.tests.messages_emulated import ( + create_test_configuration, + create_specific_config_keys, + create_config_for_delete, + create_test_subscriptions, +) + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +@pytest.fixture +def setup_collector(): + """Sets up an EmulatedCollector instance for testing.""" + yield EmulatedCollector(address="127.0.0.1", port=8080) + +@pytest.fixture +def connected_configured_collector(setup_collector): + collector = setup_collector # EmulatedCollector(address="127.0.0.1", port=8080) + collector.Connect() + collector.SetConfig(create_test_configuration()) + yield collector + collector.Disconnect() + +def test_connect(setup_collector): + logger.info(">>> test_connect <<<") + collector = setup_collector + assert collector.Connect() is True + assert collector.connected is True + +def test_disconnect(setup_collector): + logger.info(">>> test_disconnect <<<") + collector = setup_collector + collector.Connect() + assert collector.Disconnect() is True + assert collector.connected is False + +# def test_set_config(setup_collector): +# logger.info(">>> test_set_config <<<") +# collector = setup_collector +# collector.Connect() + +# config = create_test_configuration() + +# results = collector.SetConfig(config) +# assert all(result is True for result in results) + +# def test_get_config(connected_configured_collector): +# logger.info(">>> test_get_config <<<") +# resource_keys = create_specific_config_keys() +# results = connected_configured_collector.GetConfig(resource_keys) + +# for key, value in results: +# assert key in create_specific_config_keys() +# assert value is not None + +# def test_delete_config(connected_configured_collector): +# logger.info(">>> test_delete_config <<<") +# resource_keys = create_config_for_delete() + +# results = connected_configured_collector.DeleteConfig(resource_keys) +# assert all(result is True for result in results) + +def test_subscribe_state(connected_configured_collector): + logger.info(">>> test_subscribe_state <<<") + subscriptions = create_test_subscriptions() + + results = connected_configured_collector.SubscribeState(subscriptions) + # logger.info(f"Subscribed result: {results}.") + assert results == [False, True, True] # all(result is True for result in results) + +def test_unsubscribe_state(connected_configured_collector): + logger.info(">>> test_unsubscribe_state <<<") + subscriptions = create_test_subscriptions() + + connected_configured_collector.SubscribeState(subscriptions) + results = connected_configured_collector.UnsubscribeState(subscriptions) + assert results == [False, True, True] # all(result is True for result in results) + +def test_get_state(connected_configured_collector): + logger.info(">>> test_get_state <<<") + subscriptions = create_test_subscriptions() + + connected_configured_collector.SubscribeState(subscriptions) + logger.info(f"Subscribed to state: {subscriptions}. waiting for 12 seconds ...") + time.sleep(12) + + state_iterator = connected_configured_collector.GetState(blocking=False) + states = list(state_iterator) + + assert len(states) > 0 diff --git a/src/telemetry/frontend/service/TelemetryFrontendServiceServicerImpl.py b/src/telemetry/frontend/service/TelemetryFrontendServiceServicerImpl.py index ce55f80510b3ff678ead1be82bda9101cc8e7e17..f74e97ffd4998ca0b3255ca4e1ebe496ebc6737b 100644 --- a/src/telemetry/frontend/service/TelemetryFrontendServiceServicerImpl.py +++ b/src/telemetry/frontend/service/TelemetryFrontendServiceServicerImpl.py @@ -74,7 +74,7 @@ class TelemetryFrontendServiceServicerImpl(TelemetryFrontendServiceServicer): "interval": collector_obj.interval_s } self.kafka_producer.produce( - KafkaTopic.REQUEST.value, + KafkaTopic.TELEMETRY_REQUEST.value, key = collector_uuid, value = json.dumps(collector_to_generate), callback = self.delivery_callback @@ -110,7 +110,7 @@ class TelemetryFrontendServiceServicerImpl(TelemetryFrontendServiceServicer): "interval": -1 } self.kafka_producer.produce( - KafkaTopic.REQUEST.value, + KafkaTopic.TELEMETRY_REQUEST.value, key = collector_uuid, value = json.dumps(collector_to_stop), callback = self.delivery_callback @@ -168,7 +168,7 @@ class TelemetryFrontendServiceServicerImpl(TelemetryFrontendServiceServicer): """ listener for response on Kafka topic. """ - self.kafka_consumer.subscribe([KafkaTopic.RESPONSE.value]) + self.kafka_consumer.subscribe([KafkaTopic.TELEMETRY_RESPONSE.value]) while True: receive_msg = self.kafka_consumer.poll(2.0) if receive_msg is None: @@ -196,7 +196,7 @@ class TelemetryFrontendServiceServicerImpl(TelemetryFrontendServiceServicer): def process_response(self, collector_id: str, kpi_id: str, kpi_value: Any): if kpi_id == "-1" and kpi_value == -1: # print ("Backend termination confirmation for collector id: ", collector_id) - LOGGER.info("Backend termination confirmation for collector id: ", collector_id) + LOGGER.info("Backend termination confirmation for collector id: {:}".format(collector_id)) else: - LOGGER.info("Backend termination confirmation for collector id: ", collector_id) + LOGGER.info("Backend termination confirmation for collector id: {:}".format(collector_id)) # print ("KPI Value: Collector Id:", collector_id, ", Kpi Id:", kpi_id, ", Value:", kpi_value)