diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 02d30d347ff9c4d5e6ec63cbc8b18ca5d97dbd23..329f321f8f1ea742476936773c3ce84d61431ca6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,6 +52,8 @@ include: #- local: '/src/telemetry/.gitlab-ci.yml' #- local: '/src/analytics/.gitlab-ci.yml' #- local: '/src/qos_profile/.gitlab-ci.yml' + #- local: '/src/vnt_manager/.gitlab-ci.yml' + #- local: '/src/e2e_orchestrator/.gitlab-ci.yml' # This should be last one: end-to-end integration tests - local: '/src/tests/.gitlab-ci.yml' diff --git a/deploy/crdb.sh b/deploy/crdb.sh index 3fcbb5cfaa1cbfcfdef14e372a871a7dd545887b..42b49fe984d08c8fb2cae14e68f0a6d2a7a726dd 100755 --- a/deploy/crdb.sh +++ b/deploy/crdb.sh @@ -175,6 +175,7 @@ function crdb_drop_databases_single() { --execute "SHOW DATABASES;" --format=tsv | awk '{print $1}' | grep "^tfs" ) echo "Found TFS databases: ${DATABASES}" | tr '\n' ' ' + echo for DB_NAME in $DATABASES; do echo "Dropping TFS database: $DB_NAME" @@ -369,6 +370,7 @@ function crdb_drop_databases_cluster() { --execute "SHOW DATABASES;" --format=tsv | awk '{print $1}' | grep "^tfs" ) echo "Found TFS databases: ${DATABASES}" | tr '\n' ' ' + echo for DB_NAME in $DATABASES; do echo "Dropping TFS database: $DB_NAME" diff --git a/deploy/tfs.sh b/deploy/tfs.sh index 65c1e8de28f2045b2ac78938b84d3c33e282025e..6c0ddcb63e71abd2e713e4809aeb7795b43053fa 100755 --- a/deploy/tfs.sh +++ b/deploy/tfs.sh @@ -344,11 +344,10 @@ for COMPONENT in $TFS_COMPONENTS; do VERSION=$(grep -i "${GITLAB_REPO_URL}/${COMPONENT}-gateway:" "$MANIFEST" | cut -d ":" -f4) sed -E -i "s#image: $GITLAB_REPO_URL/$COMPONENT-gateway:${VERSION}#image: $IMAGE_URL#g" "$MANIFEST" else + VERSION=$(grep -i "${GITLAB_REPO_URL}/${COMPONENT}:" "$MANIFEST" | cut -d ":" -f4) if [ "$TFS_SKIP_BUILD" != "YES" ]; then IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT:$TFS_IMAGE_TAG" | sed 's,//,/,g' | sed 's,http:/,,g') - VERSION=$(grep -i "${GITLAB_REPO_URL}/${COMPONENT}:" "$MANIFEST" | cut -d ":" -f4) else - VERSION=$(grep -i "${GITLAB_REPO_URL}/${COMPONENT}:" "$MANIFEST" | cut -d ":" -f4) IMAGE_URL=$(echo "$TFS_REGISTRY_IMAGES/$COMPONENT:$VERSION" | sed 's,//,/,g' | sed 's,http:/,,g') fi sed -E -i "s#image: $GITLAB_REPO_URL/$COMPONENT:${VERSION}#image: $IMAGE_URL#g" "$MANIFEST" diff --git a/ecoc24 b/ecoc24 new file mode 120000 index 0000000000000000000000000000000000000000..37c97d3a77727ec098303cc9a5e04d711a30fcec --- /dev/null +++ b/ecoc24 @@ -0,0 +1 @@ +src/tests/ecoc24/ \ No newline at end of file diff --git a/manifests/analyticsservice.yaml b/manifests/analyticsservice.yaml index 61666ead951c73e4034110b00a51743d33bd4ce2..536bb185286ba5444ad22d17d00706a066172e4c 100644 --- a/manifests/analyticsservice.yaml +++ b/manifests/analyticsservice.yaml @@ -98,11 +98,11 @@ spec: selector: app: analyticsservice ports: - - name: frontend-grpc + - name: grpc protocol: TCP port: 30080 targetPort: 30080 - - name: backend-grpc + - name: grpc-backend protocol: TCP port: 30090 targetPort: 30090 diff --git a/manifests/e2e_orchestratorservice.yaml b/manifests/e2e_orchestratorservice.yaml index b6354c7a8aef7dc565b61d84703f905055b9303f..5f70fdfdac08112650afc7bc50f02a7eedd5a30a 100644 --- a/manifests/e2e_orchestratorservice.yaml +++ b/manifests/e2e_orchestratorservice.yaml @@ -20,8 +20,12 @@ spec: selector: matchLabels: app: e2e-orchestratorservice + replicas: 1 template: metadata: + annotations: + config.linkerd.io/skip-outbound-ports: "8761" + config.linkerd.io/skip-inbound-ports: "8761" labels: app: e2e-orchestratorservice spec: @@ -33,9 +37,18 @@ spec: ports: - containerPort: 10050 - containerPort: 9192 + - containerPort: 8761 env: - name: LOG_LEVEL value: "INFO" + - name: WS_IP_HOST + value: "nbiservice.tfs-ip.svc.cluster.local" + - name: WS_IP_PORT + value: 8761 + - name: WS_E2E_HOST + value: "e2e-orchestratorservice.tfs-e2e.svc.cluster.local" + - name: WS_E2E_PORT + value: 8762 readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:10050"] @@ -67,25 +80,6 @@ spec: - name: metrics port: 9192 targetPort: 9192 ---- -apiVersion: autoscaling/v2 -kind: HorizontalPodAutoscaler -metadata: - name: e2e-orchestratorservice-hpa -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: e2e-orchestratorservice - minReplicas: 1 - maxReplicas: 20 - metrics: - - type: Resource - resource: - name: cpu - target: - type: Utilization - averageUtilization: 80 - #behavior: - # scaleDown: - # stabilizationWindowSeconds: 30 + - name: ws + port: 8761 + targetPort: 8761 diff --git a/manifests/nbiservice.yaml b/manifests/nbiservice.yaml index 70f553e6425ca7972b8af185f432842b4e184790..a514736c14a9bdd1f64301e5566719bb5ee5c0c7 100644 --- a/manifests/nbiservice.yaml +++ b/manifests/nbiservice.yaml @@ -23,6 +23,9 @@ spec: replicas: 1 template: metadata: + annotations: + config.linkerd.io/skip-inbound-ports: "8762" + config.linkerd.io/skip-outbound-ports: "8762" labels: app: nbiservice spec: @@ -35,11 +38,14 @@ spec: - containerPort: 8080 - containerPort: 9090 - containerPort: 9192 + - containerPort: 8762 env: - name: LOG_LEVEL value: "INFO" - name: IETF_NETWORK_RENDERER value: "LIBYANG" + - name: WS_E2E_PORT + value: 8762 readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:9090"] @@ -77,3 +83,7 @@ spec: protocol: TCP port: 9192 targetPort: 9192 + - name: ws + protocol: TCP + port: 8762 + targetPort: 8762 diff --git a/manifests/telemetryservice.yaml b/manifests/telemetryservice.yaml index cd35d2698816bcfc5bc2030506eb2897a85708f6..86d864157838513dd68f10679d44d11b074c422c 100644 --- a/manifests/telemetryservice.yaml +++ b/manifests/telemetryservice.yaml @@ -98,11 +98,11 @@ spec: selector: app: telemetryservice ports: - - name: frontend-grpc + - name: grpc protocol: TCP port: 30050 targetPort: 30050 - - name: backend-grpc + - name: grpc-backend protocol: TCP port: 30060 targetPort: 30060 diff --git a/manifests/vnt_managerservice.yaml b/manifests/vnt_managerservice.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2e31c743902da3dd6ab9354f5b7617d4e884751e --- /dev/null +++ b/manifests/vnt_managerservice.yaml @@ -0,0 +1,77 @@ +# 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. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vnt-managerservice +spec: + selector: + matchLabels: + app: vnt-managerservice + replicas: 1 + template: + metadata: + annotations: + config.linkerd.io/skip-outbound-ports: "8765" + config.linkerd.io/skip-inbound-ports: "8765" + labels: + app: vnt-managerservice + spec: + terminationGracePeriodSeconds: 5 + containers: + - name: server + image: labs.etsi.org:5050/tfs/controller/vnt_manager:latest + imagePullPolicy: Always + ports: + - containerPort: 10080 + - containerPort: 9192 + env: + - name: LOG_LEVEL + value: "INFO" + - name: WS_IP_PORT + value: 8761 + - name: WS_E2E_PORT + value: 8762 + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:10080"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:10080"] + resources: + requests: + cpu: 250m + memory: 128Mi + limits: + cpu: 1000m + memory: 1024Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: vnt-managerservice + labels: + app: vnt-managerservice +spec: + type: ClusterIP + selector: + app: vnt-managerservice + ports: + - name: grpc + port: 10080 + targetPort: 10080 + - name: metrics + port: 9192 + targetPort: 9192 diff --git a/my_deploy.sh b/my_deploy.sh index 344ca44ee335e73dcc7b8f8c9ca71ead7d90880f..8d2e733d462ae743c7187eb9b6a58d7da14033a7 100755 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -20,7 +20,7 @@ export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/" # Set the list of components, separated by spaces, you want to build images for, and deploy. -export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_generator" +export TFS_COMPONENTS="context device pathcomp service slice nbi webui" # Uncomment to activate Monitoring (old) #export TFS_COMPONENTS="${TFS_COMPONENTS} monitoring" @@ -65,6 +65,9 @@ export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_gene # Uncomment to activate E2E Orchestrator #export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" +# Uncomment to activate VNT Manager +#export TFS_COMPONENTS="${TFS_COMPONENTS} vnt_manager" + # Uncomment to activate DLT and Interdomain #export TFS_COMPONENTS="${TFS_COMPONENTS} interdomain dlt" #if [[ "$TFS_COMPONENTS" == *"dlt"* ]]; then @@ -83,6 +86,9 @@ export TFS_COMPONENTS="context device pathcomp service slice nbi webui load_gene # export TFS_COMPONENTS="${BEFORE} qkd_app service ${AFTER}" #fi +# Uncomment to activate Load Generator +#export TFS_COMPONENTS="${TFS_COMPONENTS} load_generator" + # Set the tag you want to use for your images. export TFS_IMAGE_TAG="dev" diff --git a/proto/context.proto b/proto/context.proto index 85972d956a93dfe09ec9a955cf304c8b3e298bb3..d3888e77b0256f63fb4b4eb10b987137057a0aa7 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -260,6 +260,7 @@ message Link { string name = 2; repeated EndPointId link_endpoint_ids = 3; LinkAttributes attributes = 4; + LinkTypeEnum link_type = 5; } message LinkIdList { @@ -275,6 +276,13 @@ message LinkEvent { LinkId link_id = 2; } +enum LinkTypeEnum { + LINKTYPE_UNKNOWN = 0; + LINKTYPE_COPPER = 1; + LINKTYPE_VIRTUAL_COPPER = 2; + LINKTYPE_OPTICAL = 3; + LINKTYPE_VIRTUAL_OPTICAL = 4; +} // ----- Service ------------------------------------------------------------------------------------------------------- message ServiceId { diff --git a/proto/e2eorchestrator.proto b/proto/e2eorchestrator.proto index d8f539f9fa7a7adaeaf48add127dd3077c904375..731016934300a45e6664b90e73ba458cb03ecfb7 100644 --- a/proto/e2eorchestrator.proto +++ b/proto/e2eorchestrator.proto @@ -20,7 +20,8 @@ import "context.proto"; service E2EOrchestratorService { - rpc Compute(E2EOrchestratorRequest) returns (E2EOrchestratorReply) {} + rpc Compute(E2EOrchestratorRequest) returns (E2EOrchestratorReply) {} + rpc PushTopology(context.Topology) returns (context.Empty) {} } message E2EOrchestratorRequest { diff --git a/proto/vnt_manager.proto b/proto/vnt_manager.proto new file mode 100644 index 0000000000000000000000000000000000000000..1e1d67e122fa0cbb7088b366c5001a712a38a1f0 --- /dev/null +++ b/proto/vnt_manager.proto @@ -0,0 +1,37 @@ +// 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. + +// protocol buffers documentation: https://developers.google.com/protocol-buffers/docs/proto3 +syntax = "proto3"; +package vnt_manager; +import "context.proto"; + + +service VNTManagerService { + rpc VNTSubscript (VNTSubscriptionRequest) returns (VNTSubscriptionReply) {} + rpc ListVirtualLinkIds (context.Empty) returns (context.LinkIdList) {} + rpc ListVirtualLinks (context.Empty) returns (context.LinkList) {} + rpc GetVirtualLink (context.LinkId) returns (context.Link) {} + rpc SetVirtualLink (context.Link) returns (context.LinkId) {} + rpc RemoveVirtualLink (context.LinkId) returns (context.Empty) {} +} + +message VNTSubscriptionRequest { + string host = 1; + string port = 2; +} + +message VNTSubscriptionReply { + string subscription = 1; +} diff --git a/scripts/show_logs_automation.sh b/scripts/show_logs_automation.sh index 8a0e417d9a7ddf1ffe0b4e4529606683ae600ecd..26684298091403f4dc737fc0d1ca5b05d82ad374 100755 --- a/scripts/show_logs_automation.sh +++ b/scripts/show_logs_automation.sh @@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} # Automated steps start here ######################################################################################################################## -kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/ztpservice +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/automationservice diff --git a/scripts/show_logs_e2eorchestrator.sh b/scripts/show_logs_e2eorchestrator.sh old mode 100644 new mode 100755 index 5ac39c6cba06e57720368dd37454986fdc0e1e29..bf1fb5987e4a65fd231f057f71cc3ffcffc14655 --- a/scripts/show_logs_e2eorchestrator.sh +++ b/scripts/show_logs_e2eorchestrator.sh @@ -24,4 +24,4 @@ export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} # Automated steps start here ######################################################################################################################## -kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/e2eorchestratorservice -c server +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/e2e-orchestratorservice -c server diff --git a/scripts/show_logs_vntmanager.sh b/scripts/show_logs_vntmanager.sh new file mode 100755 index 0000000000000000000000000000000000000000..0dba86567bd5118b685c72fd17b1904acfe698cc --- /dev/null +++ b/scripts/show_logs_vntmanager.sh @@ -0,0 +1,27 @@ +#!/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. + +######################################################################################################################## +# Define your deployment settings here +######################################################################################################################## + +# If not already set, set the name of the Kubernetes namespace to deploy to. +export TFS_K8S_NAMESPACE=${TFS_K8S_NAMESPACE:-"tfs"} + +######################################################################################################################## +# Automated steps start here +######################################################################################################################## + +kubectl --namespace $TFS_K8S_NAMESPACE logs deployment/vnt_managerservice -c server diff --git a/scripts/update_license_headers.py b/scripts/update_license_headers.py index 1de9f9b7c2795505aba53276a0deb2acdcccc703..34d620ed8bc1a0817509916c2d1f857979cdd431 100644 --- a/scripts/update_license_headers.py +++ b/scripts/update_license_headers.py @@ -32,6 +32,7 @@ STR_NEW_COPYRIGHT = 'Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https:/ RE_OLD_COPYRIGHTS = [ r'Copyright\ 2021\-2023\ H2020\ TeraFlow\ \(https\:\/\/www\.teraflow\-h2020\.eu\/\)', r'Copyright\ 2022\-2023\ ETSI\ TeraFlowSDN\ \-\ TFS\ OSG\ \(https\:\/\/tfs\.etsi\.org\/\)', + r'Copyright\ 2022\-2024\ ETSI\ TeraFlowSDN\ \-\ TFS\ OSG\ \(https\:\/\/tfs\.etsi\.org\/\)', ] RE_OLD_COPYRIGHTS = [ (re.compile(r'.*{}.*'.format(re_old_copyright)), re.compile(re_old_copyright)) diff --git a/src/analytics/frontend/client/AnalyticsFrontendClient.py b/src/analytics/frontend/client/AnalyticsFrontendClient.py index 90e95d661d46f24ae5ffaeb7bcfa19b7e1f36526..809c957ea48a07a657fe1edc244c9c0f125e9058 100644 --- a/src/analytics/frontend/client/AnalyticsFrontendClient.py +++ b/src/analytics/frontend/client/AnalyticsFrontendClient.py @@ -28,8 +28,8 @@ RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, class AnalyticsFrontendClient: def __init__(self, host=None, port=None): - if not host: host = get_service_host(ServiceNameEnum.ANALYTICSFRONTEND) - if not port: port = get_service_port_grpc(ServiceNameEnum.ANALYTICSFRONTEND) + if not host: host = get_service_host(ServiceNameEnum.ANALYTICS) + if not port: port = get_service_port_grpc(ServiceNameEnum.ANALYTICS) self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) LOGGER.debug('Creating channel to {:s}...'.format(str(self.endpoint))) self.channel = None diff --git a/src/analytics/frontend/service/AnalyticsFrontendService.py b/src/analytics/frontend/service/AnalyticsFrontendService.py index 42a7fc9b60418c1c0fc5af6f320ae5c330ce8871..8d2536fe091459d6026941f4eae52f58f7cd3f3a 100644 --- a/src/analytics/frontend/service/AnalyticsFrontendService.py +++ b/src/analytics/frontend/service/AnalyticsFrontendService.py @@ -20,7 +20,7 @@ from analytics.frontend.service.AnalyticsFrontendServiceServicerImpl import Anal class AnalyticsFrontendService(GenericGrpcService): def __init__(self, cls_name: str = __name__): - port = get_service_port_grpc(ServiceNameEnum.ANALYTICSFRONTEND) + port = get_service_port_grpc(ServiceNameEnum.ANALYTICS) super().__init__(port, cls_name=cls_name) self.analytics_frontend_servicer = AnalyticsFrontendServiceServicerImpl() diff --git a/src/analytics/frontend/tests/test_frontend.py b/src/analytics/frontend/tests/test_frontend.py index 48ab4dac5a5dfbdec688fc5c346f95d41e32c81c..74fef6c79cc2328b65671b392220ae86106e9d5d 100644 --- a/src/analytics/frontend/tests/test_frontend.py +++ b/src/analytics/frontend/tests/test_frontend.py @@ -41,9 +41,9 @@ from apscheduler.triggers.interval import IntervalTrigger LOCAL_HOST = '127.0.0.1' -ANALYTICS_FRONTEND_PORT = str(get_service_port_grpc(ServiceNameEnum.ANALYTICSFRONTEND)) -os.environ[get_env_var_name(ServiceNameEnum.ANALYTICSFRONTEND, ENVVAR_SUFIX_SERVICE_HOST )] = str(LOCAL_HOST) -os.environ[get_env_var_name(ServiceNameEnum.ANALYTICSFRONTEND, ENVVAR_SUFIX_SERVICE_PORT_GRPC)] = str(ANALYTICS_FRONTEND_PORT) +ANALYTICS_FRONTEND_PORT = str(get_service_port_grpc(ServiceNameEnum.ANALYTICS)) +os.environ[get_env_var_name(ServiceNameEnum.ANALYTICS, ENVVAR_SUFIX_SERVICE_HOST )] = str(LOCAL_HOST) +os.environ[get_env_var_name(ServiceNameEnum.ANALYTICS, ENVVAR_SUFIX_SERVICE_PORT_GRPC)] = str(ANALYTICS_FRONTEND_PORT) LOGGER = logging.getLogger(__name__) diff --git a/src/automation/service/EventEngine.py b/src/automation/service/EventEngine.py new file mode 100644 index 0000000000000000000000000000000000000000..26c2b28cbe35230beec90dd9df4112d4ad131876 --- /dev/null +++ b/src/automation/service/EventEngine.py @@ -0,0 +1,169 @@ +# 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, logging, queue, threading +from typing import Dict, Optional +from automation.service.Tools import create_kpi_descriptor, start_collector +from common.proto.context_pb2 import ( + ConfigActionEnum, DeviceEvent, DeviceOperationalStatusEnum, Empty, ServiceEvent +) +from common.proto.kpi_sample_types_pb2 import KpiSampleType +from common.tools.grpc.BaseEventCollector import BaseEventCollector +from common.tools.grpc.BaseEventDispatcher import BaseEventDispatcher +from common.tools.grpc.Tools import grpc_message_to_json_string +from context.client.ContextClient import ContextClient +from kpi_manager.client.KpiManagerClient import KpiManagerClient +from telemetry.frontend.client.TelemetryFrontendClient import TelemetryFrontendClient + +LOGGER = logging.getLogger(__name__) + +DEVICE_OP_STATUS_UNDEFINED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED +DEVICE_OP_STATUS_DISABLED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED +DEVICE_OP_STATUS_ENABLED = DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED +DEVICE_OP_STATUS_NOT_ENABLED = {DEVICE_OP_STATUS_UNDEFINED, DEVICE_OP_STATUS_DISABLED} + +KPISAMPLETYPE_UNKNOWN = KpiSampleType.KPISAMPLETYPE_UNKNOWN + +class EventCollector(BaseEventCollector): + pass + +class EventDispatcher(BaseEventDispatcher): + def __init__( + self, events_queue : queue.PriorityQueue, + terminate : Optional[threading.Event] = None + ) -> None: + super().__init__(events_queue, terminate) + self._context_client = ContextClient() + self._kpi_manager_client = KpiManagerClient() + self._telemetry_client = TelemetryFrontendClient() + self._device_endpoint_monitored : Dict[str, Dict[str, bool]] = dict() + + def dispatch_device_create(self, device_event : DeviceEvent) -> None: + MSG = 'Processing Device Create: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(device_event))) + self._device_activate_monitoring(device_event) + + def dispatch_device_update(self, device_event : DeviceEvent) -> None: + MSG = 'Processing Device Update: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(device_event))) + self._device_activate_monitoring(device_event) + + def dispatch_device_remove(self, device_event : DeviceEvent) -> None: + MSG = 'Processing Device Remove: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(device_event))) + + def dispatch_service_create(self, service_event : ServiceEvent) -> None: + MSG = 'Processing Service Create: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(service_event))) + + def dispatch_service_update(self, service_event : ServiceEvent) -> None: + MSG = 'Processing Service Update: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(service_event))) + + def dispatch_service_remove(self, service_event : ServiceEvent) -> None: + MSG = 'Processing Service Remove: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(service_event))) + + def _device_activate_monitoring(self, device_event : DeviceEvent) -> None: + device_id = device_event.device_id + device_uuid = device_id.device_uuid.uuid + device = self._context_client.GetDevice(device_id) + + device_op_status = device.device_operational_status + if device_op_status != DEVICE_OP_STATUS_ENABLED: + LOGGER.debug('Ignoring Device not enabled: {:s}'.format(grpc_message_to_json_string(device))) + return + + enabled_endpoint_names = set() + for config_rule in device.device_config.config_rules: + if config_rule.action != ConfigActionEnum.CONFIGACTION_SET: continue + if config_rule.WhichOneof('config_rule') != 'custom': continue + str_resource_key = str(config_rule.custom.resource_key) + if not str_resource_key.startswith('/interface['): continue + json_resource_value = json.loads(config_rule.custom.resource_value) + if 'name' not in json_resource_value: continue + if 'enabled' not in json_resource_value: continue + if not json_resource_value['enabled']: continue + enabled_endpoint_names.add(json_resource_value['name']) + + endpoints_monitored = self._device_endpoint_monitored.setdefault(device_uuid, dict()) + for endpoint in device.device_endpoints: + endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid + endpoint_name_or_uuid = endpoint.name + if endpoint_name_or_uuid is None or len(endpoint_name_or_uuid) == 0: + endpoint_name_or_uuid = endpoint_uuid + + endpoint_was_monitored = endpoints_monitored.get(endpoint_uuid, False) + endpoint_is_enabled = (endpoint_name_or_uuid in enabled_endpoint_names) + + if not endpoint_was_monitored and endpoint_is_enabled: + # activate + for kpi_sample_type in endpoint.kpi_sample_types: + if kpi_sample_type == KPISAMPLETYPE_UNKNOWN: continue + + kpi_id = create_kpi_descriptor( + self._kpi_manager_client, kpi_sample_type, + device_id=device.device_id, + endpoint_id=endpoint.endpoint_id, + ) + + duration_seconds = 86400 + interval_seconds = 10 + collector_id = start_collector( + self._telemetry_client, kpi_id, + duration_seconds, interval_seconds + ) + + endpoints_monitored[endpoint_uuid] = True + else: + MSG = 'Not implemented condition: event={:s} device={:s} endpoint={:s}' + \ + ' endpoint_was_monitored={:s} endpoint_is_enabled={:s}' + LOGGER.warning(MSG.format( + grpc_message_to_json_string(device_event), grpc_message_to_json_string(device), + grpc_message_to_json_string(endpoint), str(endpoint_was_monitored), + str(endpoint_is_enabled) + )) + +class EventEngine: + def __init__( + self, terminate : Optional[threading.Event] = None + ) -> None: + self._terminate = threading.Event() if terminate is None else terminate + + self._context_client = ContextClient() + self._event_collector = EventCollector(terminate=self._terminate) + self._event_collector.install_collector( + self._context_client.GetDeviceEvents, Empty(), + log_events_received=True + ) + self._event_collector.install_collector( + self._context_client.GetServiceEvents, Empty(), + log_events_received=True + ) + + self._event_dispatcher = EventDispatcher( + self._event_collector.get_events_queue(), + terminate=self._terminate + ) + + def start(self) -> None: + self._context_client.connect() + self._event_collector.start() + self._event_dispatcher.start() + + def stop(self) -> None: + self._terminate.set() + self._event_dispatcher.stop() + self._event_collector.stop() + self._context_client.close() diff --git a/src/automation/service/Tools.py b/src/automation/service/Tools.py new file mode 100644 index 0000000000000000000000000000000000000000..6a63475ca23c576a6fe946d6d149b70465ff1e1f --- /dev/null +++ b/src/automation/service/Tools.py @@ -0,0 +1,64 @@ +# 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, uuid +from typing import Optional +from common.proto.context_pb2 import ConnectionId, DeviceId, EndPointId, LinkId, ServiceId, SliceId +from common.proto.kpi_manager_pb2 import KpiDescriptor, KpiId +from common.proto.kpi_sample_types_pb2 import KpiSampleType +from common.proto.telemetry_frontend_pb2 import Collector, CollectorId +from kpi_manager.client.KpiManagerClient import KpiManagerClient +from telemetry.frontend.client.TelemetryFrontendClient import TelemetryFrontendClient + +LOGGER = logging.getLogger(__name__) + +def create_kpi_descriptor( + kpi_manager_client : KpiManagerClient, + kpi_sample_type : KpiSampleType, + device_id : Optional[DeviceId ] = None, + endpoint_id : Optional[EndPointId ] = None, + service_id : Optional[ServiceId ] = None, + slice_id : Optional[SliceId ] = None, + connection_id : Optional[ConnectionId] = None, + link_id : Optional[LinkId ] = None, +) -> KpiId: + kpi_descriptor = KpiDescriptor() + kpi_descriptor.kpi_id.kpi_id.uuid = str(uuid.uuid4()) + kpi_descriptor.kpi_description = '' + kpi_descriptor.kpi_sample_type = kpi_sample_type + + if device_id is not None: kpi_descriptor.device_id .CopyFrom(device_id ) + if endpoint_id is not None: kpi_descriptor.endpoint_id .CopyFrom(endpoint_id ) + if service_id is not None: kpi_descriptor.service_id .CopyFrom(service_id ) + if slice_id is not None: kpi_descriptor.slice_id .CopyFrom(slice_id ) + if connection_id is not None: kpi_descriptor.connection_id.CopyFrom(connection_id) + if link_id is not None: kpi_descriptor.link_id .CopyFrom(link_id ) + + kpi_id : KpiId = kpi_manager_client.SetKpiDescriptor(kpi_descriptor) + return kpi_id + +def start_collector( + telemetry_client : TelemetryFrontendClient, + kpi_id : KpiId, + duration_seconds : float, + interval_seconds : float +) -> CollectorId: + collector = Collector() + collector.collector_id.collector_id.uuid = str(uuid.uuid4()) + collector.kpi_id.CopyFrom(kpi_id) + collector.duration_s = duration_seconds + collector.interval_s = interval_seconds + collector_id : CollectorId = telemetry_client.StartCollector(collector) + return collector_id diff --git a/src/automation/service/__main__.py b/src/automation/service/__main__.py index 39d8beaffd959744b83d7e1ace78da2b1b800a21..3baa0bd30b19fb624c5dcf0b236642704e42ab9f 100644 --- a/src/automation/service/__main__.py +++ b/src/automation/service/__main__.py @@ -14,7 +14,13 @@ import logging, signal, sys, threading from prometheus_client import start_http_server -from common.Settings import get_log_level, get_metrics_port +from automation.service.EventEngine import EventEngine +from common.Constants import ServiceNameEnum +from common.Settings import ( + ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, + get_env_var_name, get_log_level, get_metrics_port, + wait_for_environment_variables +) from .AutomationService import AutomationService LOG_LEVEL = get_log_level() @@ -29,6 +35,22 @@ def signal_handler(signal, frame): # pylint: disable=redefined-outer-name,unused def main(): LOGGER.info('Starting...') + + wait_for_environment_variables([ + get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + get_env_var_name(ServiceNameEnum.DEVICE, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.DEVICE, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + get_env_var_name(ServiceNameEnum.KPIMANAGER, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.KPIMANAGER, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + get_env_var_name(ServiceNameEnum.TELEMETRY, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.TELEMETRY, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + get_env_var_name(ServiceNameEnum.ANALYTICS, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.ANALYTICS, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + get_env_var_name(ServiceNameEnum.POLICY, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.POLICY, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + ]) + signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) @@ -36,7 +58,11 @@ def main(): metrics_port = get_metrics_port() start_http_server(metrics_port) - # Starting context service + # Start Event Collection+Dispatching Engine + event_engine = EventEngine(terminate=terminate) + event_engine.start() + + # Starting Automation service grpc_service = AutomationService() grpc_service.start() @@ -45,6 +71,7 @@ def main(): LOGGER.info('Terminating...') grpc_service.stop() + event_engine.stop() LOGGER.info('Bye') return 0 diff --git a/src/common/Constants.py b/src/common/Constants.py index 33a5d5047c8969118417a8b06a13cb8fb6fbf7df..aa15d66c5937c3f5a589ddf9cbb432d08c4b49b0 100644 --- a/src/common/Constants.py +++ b/src/common/Constants.py @@ -61,14 +61,15 @@ class ServiceNameEnum(Enum): FORECASTER = 'forecaster' E2EORCHESTRATOR = 'e2e-orchestrator' OPTICALCONTROLLER = 'opticalcontroller' + VNTMANAGER = 'vnt-manager' BGPLS = 'bgpls-speaker' QKD_APP = 'qkd_app' KPIMANAGER = 'kpi-manager' KPIVALUEAPI = 'kpi-value-api' KPIVALUEWRITER = 'kpi-value-writer' - TELEMETRYFRONTEND = 'telemetry-frontend' + TELEMETRY = 'telemetry' TELEMETRYBACKEND = 'telemetry-backend' - ANALYTICSFRONTEND = 'analytics-frontend' + ANALYTICS = 'analytics' ANALYTICSBACKEND = 'analytics-backend' QOSPROFILE = 'qos-profile' @@ -100,14 +101,15 @@ DEFAULT_SERVICE_GRPC_PORTS = { ServiceNameEnum.E2EORCHESTRATOR .value : 10050, ServiceNameEnum.OPTICALCONTROLLER .value : 10060, ServiceNameEnum.QKD_APP .value : 10070, + ServiceNameEnum.VNTMANAGER .value : 10080, ServiceNameEnum.BGPLS .value : 20030, ServiceNameEnum.QOSPROFILE .value : 20040, ServiceNameEnum.KPIMANAGER .value : 30010, ServiceNameEnum.KPIVALUEAPI .value : 30020, ServiceNameEnum.KPIVALUEWRITER .value : 30030, - ServiceNameEnum.TELEMETRYFRONTEND .value : 30050, + ServiceNameEnum.TELEMETRY .value : 30050, ServiceNameEnum.TELEMETRYBACKEND .value : 30060, - ServiceNameEnum.ANALYTICSFRONTEND .value : 30080, + ServiceNameEnum.ANALYTICS .value : 30080, ServiceNameEnum.ANALYTICSBACKEND .value : 30090, ServiceNameEnum.AUTOMATION .value : 30200, diff --git a/src/common/tools/grpc/BaseEventCollector.py b/src/common/tools/grpc/BaseEventCollector.py new file mode 100644 index 0000000000000000000000000000000000000000..04dfb654963da1ae4f83a8a14feaaa8c17d1f128 --- /dev/null +++ b/src/common/tools/grpc/BaseEventCollector.py @@ -0,0 +1,136 @@ +# 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. + +# See usage example below + +import grpc, logging, queue, threading, time +from typing import Any, Callable, List, Optional +from common.proto.context_pb2 import Empty +from common.tools.grpc.Tools import grpc_message_to_json_string +from context.client.ContextClient import ContextClient + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) + +class CollectorThread(threading.Thread): + def __init__( + self, subscription_func : Callable, events_queue = queue.PriorityQueue, + terminate = threading.Event, log_events_received: bool = False + ) -> None: + super().__init__(daemon=False) + self._subscription_func = subscription_func + self._events_queue = events_queue + self._terminate = terminate + self._log_events_received = log_events_received + self._stream = None + + def cancel(self) -> None: + if self._stream is None: return + self._stream.cancel() + + def run(self) -> None: + while not self._terminate.is_set(): + self._stream = self._subscription_func() + try: + for event in self._stream: + if self._log_events_received: + str_event = grpc_message_to_json_string(event) + LOGGER.info('[_collect] event: {:s}'.format(str_event)) + timestamp = event.event.timestamp.timestamp + self._events_queue.put_nowait((timestamp, event)) + except grpc.RpcError as e: + if e.code() == grpc.StatusCode.UNAVAILABLE: # pylint: disable=no-member + LOGGER.info('[_collect] UNAVAILABLE... retrying...') + time.sleep(0.5) + continue + elif e.code() == grpc.StatusCode.CANCELLED: # pylint: disable=no-member + break + else: + raise # pragma: no cover + +class BaseEventCollector: + def __init__( + self, terminate : Optional[threading.Event] = None + ) -> None: + self._events_queue = queue.PriorityQueue() + self._terminate = threading.Event() if terminate is None else terminate + self._collector_threads : List[CollectorThread] = list() + + def install_collector( + self, subscription_method : Callable, request_message : Any, + log_events_received : bool = False + ) -> None: + self._collector_threads.append(CollectorThread( + lambda: subscription_method(request_message), + self._events_queue, self._terminate, log_events_received + )) + + def start(self): + self._terminate.clear() + for collector_thread in self._collector_threads: + collector_thread.start() + + def stop(self): + self._terminate.set() + + for collector_thread in self._collector_threads: + collector_thread.cancel() + + for collector_thread in self._collector_threads: + collector_thread.join() + + def get_events_queue(self) -> queue.PriorityQueue: + return self._events_queue + + def get_event(self, block : bool = True, timeout : float = 0.1): + try: + _,event = self._events_queue.get(block=block, timeout=timeout) + return event + except queue.Empty: # pylint: disable=catching-non-exception + return None + + def get_events(self, block : bool = True, timeout : float = 0.1, count : int = None): + events = [] + if count is None: + while not self._terminate.is_set(): + event = self.get_event(block=block, timeout=timeout) + if event is None: break + events.append(event) + else: + while len(events) < count: + if self._terminate.is_set(): break + event = self.get_event(block=block, timeout=timeout) + if event is None: continue + events.append(event) + return sorted(events, key=lambda e: e.event.timestamp.timestamp) + +def main() -> None: + logging.basicConfig(level=logging.INFO) + + context_client = ContextClient() + context_client.connect() + + event_collector = BaseEventCollector() + event_collector.install_collector(context_client.GetDeviceEvents, Empty(), log_events_received=True) + event_collector.install_collector(context_client.GetLinkEvents, Empty(), log_events_received=True) + event_collector.install_collector(context_client.GetServiceEvents, Empty(), log_events_received=True) + event_collector.start() + + time.sleep(60) + + event_collector.stop() + context_client.close() + +if __name__ == '__main__': + main() diff --git a/src/common/tools/grpc/BaseEventDispatcher.py b/src/common/tools/grpc/BaseEventDispatcher.py new file mode 100644 index 0000000000000000000000000000000000000000..c9ec292c994bb4728043bf3bfed73e176f4f748a --- /dev/null +++ b/src/common/tools/grpc/BaseEventDispatcher.py @@ -0,0 +1,119 @@ +# 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. + +# See usage example below + +import logging, queue, threading, time +from typing import Any, Callable, Optional +from common.proto.context_pb2 import DeviceEvent, Empty, EventTypeEnum, LinkEvent +from common.tools.grpc.BaseEventCollector import BaseEventCollector +from common.tools.grpc.Tools import grpc_message_to_json_string +from context.client.ContextClient import ContextClient + +LOGGER = logging.getLogger(__name__) + +class BaseEventDispatcher(threading.Thread): + def __init__( + self, events_queue : queue.PriorityQueue, + terminate : Optional[threading.Event] = None + ) -> None: + super().__init__(daemon=True) + self._events_queue = events_queue + self._terminate = threading.Event() if terminate is None else terminate + + def stop(self): + self._terminate.set() + + def _get_event(self, block : bool = True, timeout : Optional[float] = 0.5) -> Optional[Any]: + try: + _, event = self._events_queue.get(block=block, timeout=timeout) + return event + except queue.Empty: + return None + + def _get_dispatcher(self, event : Any) -> Optional[Callable]: + object_name = str(event.__class__.__name__).lower().replace('event', '') + event_type = EventTypeEnum.Name(event.event.event_type).lower().replace('eventtype_', '') + + method_name = 'dispatch_{:s}_{:s}'.format(object_name, event_type) + dispatcher = getattr(self, method_name, None) + if dispatcher is not None: return dispatcher + + method_name = 'dispatch_{:s}'.format(object_name) + dispatcher = getattr(self, method_name, None) + if dispatcher is not None: return dispatcher + + method_name = 'dispatch' + dispatcher = getattr(self, method_name, None) + if dispatcher is not None: return dispatcher + + return None + + def run(self) -> None: + while not self._terminate.is_set(): + event = self._get_event() + if event is None: continue + + dispatcher = self._get_dispatcher(event) + if dispatcher is None: + MSG = 'No dispatcher available for Event({:s})' + LOGGER.warning(MSG.format(grpc_message_to_json_string(event))) + continue + + dispatcher(event) + +class MyEventDispatcher(BaseEventDispatcher): + def dispatch_device_create(self, device_event : DeviceEvent) -> None: + MSG = 'Processing Device Create: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(device_event))) + + def dispatch_device_update(self, device_event : DeviceEvent) -> None: + MSG = 'Processing Device Update: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(device_event))) + + def dispatch_device_remove(self, device_event : DeviceEvent) -> None: + MSG = 'Processing Device Remove: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(device_event))) + + def dispatch_link(self, link_event : LinkEvent) -> None: + MSG = 'Processing Link Create/Update/Remove: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(link_event))) + + def dispatch(self, event : Any) -> None: + MSG = 'Processing any other Event: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(event))) + +def main() -> None: + logging.basicConfig(level=logging.INFO) + + context_client = ContextClient() + context_client.connect() + + event_collector = BaseEventCollector() + event_collector.install_collector(context_client.GetDeviceEvents, Empty(), log_events_received=True) + event_collector.install_collector(context_client.GetLinkEvents, Empty(), log_events_received=True) + event_collector.install_collector(context_client.GetServiceEvents, Empty(), log_events_received=True) + event_collector.start() + + event_dispatcher = MyEventDispatcher(event_collector.get_events_queue()) + event_dispatcher.start() + + time.sleep(60) + + event_dispatcher.stop() + event_collector.stop() + context_client.close() + +if __name__ == '__main__': + main() diff --git a/src/common/tools/grpc/ExampleEventEngine.py b/src/common/tools/grpc/ExampleEventEngine.py new file mode 100644 index 0000000000000000000000000000000000000000..f27792497db09467c0225f07d036adc8c5b5ed84 --- /dev/null +++ b/src/common/tools/grpc/ExampleEventEngine.py @@ -0,0 +1,101 @@ +# 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, threading, time +from typing import Optional +from common.proto.context_pb2 import DeviceEvent, Empty, ServiceEvent +from common.tools.grpc.BaseEventCollector import BaseEventCollector +from common.tools.grpc.BaseEventDispatcher import BaseEventDispatcher +from common.tools.grpc.Tools import grpc_message_to_json_string +from context.client.ContextClient import ContextClient + +LOGGER = logging.getLogger(__name__) + +class EventCollector(BaseEventCollector): + pass + +class EventDispatcher(BaseEventDispatcher): + def dispatch_device_create(self, device_event : DeviceEvent) -> None: + MSG = 'Processing Device Create: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(device_event))) + + def dispatch_device_update(self, device_event : DeviceEvent) -> None: + MSG = 'Processing Device Update: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(device_event))) + + def dispatch_device_remove(self, device_event : DeviceEvent) -> None: + MSG = 'Processing Device Remove: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(device_event))) + + def dispatch_service_create(self, service_event : ServiceEvent) -> None: + MSG = 'Processing Service Create: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(service_event))) + + def dispatch_service_update(self, service_event : ServiceEvent) -> None: + MSG = 'Processing Service Update: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(service_event))) + + def dispatch_service_remove(self, service_event : ServiceEvent) -> None: + MSG = 'Processing Service Remove: {:s}' + LOGGER.info(MSG.format(grpc_message_to_json_string(service_event))) + +class ExampleEventEngine: + def __init__( + self, terminate : Optional[threading.Event] = None + ) -> None: + self._terminate = threading.Event() if terminate is None else terminate + + self._context_client = ContextClient() + self._event_collector = EventCollector(terminate=self._terminate) + self._event_collector.install_collector( + self._context_client.GetDeviceEvents, Empty(), + log_events_received=True + ) + self._event_collector.install_collector( + self._context_client.GetLinkEvents, Empty(), + log_events_received=True + ) + self._event_collector.install_collector( + self._context_client.GetServiceEvents, Empty(), + log_events_received=True + ) + + self._event_dispatcher = EventDispatcher( + self._event_collector.get_events_queue(), + terminate=self._terminate + ) + + def start(self) -> None: + self._context_client.connect() + self._event_collector.start() + self._event_dispatcher.start() + + def stop(self) -> None: + self._terminate.set() + self._event_dispatcher.stop() + self._event_collector.stop() + self._context_client.close() + +def main() -> None: + logging.basicConfig(level=logging.INFO) + + event_engine = ExampleEventEngine() + event_engine.start() + + time.sleep(60) + + event_engine.stop() + +if __name__ == '__main__': + main() diff --git a/src/device/service/DeviceServiceServicerImpl.py b/src/device/service/DeviceServiceServicerImpl.py index ebbf19607a7c591f3414d0a9b276930a6b7b1c00..7546c225e67fd3122ec845b3154606eddb7cd9ff 100644 --- a/src/device/service/DeviceServiceServicerImpl.py +++ b/src/device/service/DeviceServiceServicerImpl.py @@ -251,8 +251,15 @@ class DeviceServiceServicerImpl(DeviceServiceServicer): device_id = context_client.SetDevice(device) device = context_client.GetDevice(device_id) - if request.device_operational_status != DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED: - device.device_operational_status = request.device_operational_status + ztp_service_host = get_env_var_name(ServiceNameEnum.ZTP, ENVVAR_SUFIX_SERVICE_HOST) + environment_variables = set(os.environ.keys()) + if ztp_service_host in environment_variables: + # ZTP component is deployed; accept status updates + if request.device_operational_status != DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED: + device.device_operational_status = request.device_operational_status + else: + # ZTP is not deployed; activated during AddDevice and not modified + pass t4 = time.time() # TODO: use of datastores (might be virtual ones) to enable rollbacks diff --git a/src/device/service/__main__.py b/src/device/service/__main__.py index e69b7fd3c4518b5cbc3833c457f8803e26b2c8e3..4a75d6284ac700bb1a6d6a388049824c2b301de7 100644 --- a/src/device/service/__main__.py +++ b/src/device/service/__main__.py @@ -16,8 +16,10 @@ import logging, signal, sys, threading from prometheus_client import start_http_server from common.Constants import ServiceNameEnum from common.Settings import ( - ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, get_env_var_name, get_log_level, get_metrics_port, - wait_for_environment_variables) + ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, + get_env_var_name, get_log_level, get_metrics_port, + wait_for_environment_variables +) from .DeviceService import DeviceService from .driver_api.DriverFactory import DriverFactory from .driver_api.DriverInstanceCache import DriverInstanceCache, preload_drivers diff --git a/src/e2e_orchestrator/Dockerfile b/src/e2e_orchestrator/Dockerfile index 1ead42a2fd85316b8f8a45aded98f91d861e6b8d..132d6ba47938e1c74cee08aa3ad41ead6f992f68 100644 --- a/src/e2e_orchestrator/Dockerfile +++ b/src/e2e_orchestrator/Dockerfile @@ -77,8 +77,11 @@ RUN pip-compile --quiet --output-file=e2e_orchestrator/requirements.txt e2e_orch RUN python3 -m pip install -r e2e_orchestrator/requirements.txt # Add component files into working directory -COPY --chown=teraflow:teraflow ./src/context/. context -COPY --chown=teraflow:teraflow ./src/e2e_orchestrator/. e2e_orchestrator +COPY src/context/__init__.py context/__init__.py +COPY src/context/client/. context/client/ +COPY src/service/__init__.py service/__init__.py +COPY src/service/client/. service/client/ +COPY src/e2e_orchestrator/. e2e_orchestrator/ # Start the service ENTRYPOINT ["python", "-m", "e2e_orchestrator.service"] diff --git a/src/e2e_orchestrator/requirements.in b/src/e2e_orchestrator/requirements.in index 6553c5a41eb9bcf20b7841d8af1fb84be61a27fc..5732b1bf053301f73a37830e66eb211912d9e200 100644 --- a/src/e2e_orchestrator/requirements.in +++ b/src/e2e_orchestrator/requirements.in @@ -12,4 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -networkx \ No newline at end of file +networkx +websockets==12.0 diff --git a/src/e2e_orchestrator/service/E2EOrchestratorServiceServicerImpl.py b/src/e2e_orchestrator/service/E2EOrchestratorServiceServicerImpl.py index 0ba2eba3390ab13f38ce80affd4faaeba61e1b87..4fc0ea3ba45a85bb3b1dccb18191dc1b4e380404 100644 --- a/src/e2e_orchestrator/service/E2EOrchestratorServiceServicerImpl.py +++ b/src/e2e_orchestrator/service/E2EOrchestratorServiceServicerImpl.py @@ -12,33 +12,181 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging - -import networkx as nx -import grpc import copy - -from common.Constants import ServiceNameEnum -from common.method_wrappers.Decorator import (MetricsPool, MetricTypeEnum, safe_and_metered_rpc_method) +from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method from common.proto.e2eorchestrator_pb2 import E2EOrchestratorRequest, E2EOrchestratorReply -from common.proto.context_pb2 import Empty, Connection, EndPointId +from common.proto.context_pb2 import ( + Empty, Connection, EndPointId, Link, LinkId, TopologyDetails, Topology, Context, Service, ServiceId, + ServiceTypeEnum, ServiceStatusEnum) from common.proto.e2eorchestrator_pb2_grpc import E2EOrchestratorServiceServicer +from common.Settings import get_setting from context.client.ContextClient import ContextClient +from service.client.ServiceClient import ServiceClient from context.service.database.uuids.EndPoint import endpoint_get_uuid +from context.service.database.uuids.Device import device_get_uuid +from common.proto.vnt_manager_pb2 import VNTSubscriptionRequest +from common.tools.grpc.Tools import grpc_message_to_json_string +import grpc +import json +import logging +import networkx as nx +from threading import Thread +from websockets.sync.client import connect +from websockets.sync.server import serve +from common.Constants import DEFAULT_CONTEXT_NAME LOGGER = logging.getLogger(__name__) +logging.getLogger("websockets").propagate = True METRICS_POOL = MetricsPool("E2EOrchestrator", "RPC") + context_client: ContextClient = ContextClient() +service_client: ServiceClient = ServiceClient() + +EXT_HOST = str(get_setting('WS_IP_HOST')) +EXT_PORT = str(get_setting('WS_IP_PORT')) + +OWN_HOST = str(get_setting('WS_E2E_HOST')) +OWN_PORT = str(get_setting('WS_E2E_PORT')) + + +ALL_HOSTS = "0.0.0.0" + +class SubscriptionServer(Thread): + def __init__(self): + Thread.__init__(self) + + def run(self): + url = "ws://" + EXT_HOST + ":" + EXT_PORT + request = VNTSubscriptionRequest() + request.host = OWN_HOST + request.port = OWN_PORT + try: + LOGGER.debug("Trying to connect to {}".format(url)) + websocket = connect(url) + except Exception as ex: + LOGGER.error('Error connecting to {}'.format(url)) + else: + with websocket: + LOGGER.debug("Connected to {}".format(url)) + send = grpc_message_to_json_string(request) + websocket.send(send) + LOGGER.debug("Sent: {}".format(send)) + try: + message = websocket.recv() + LOGGER.debug("Received message from WebSocket: {}".format(message)) + except Exception as ex: + LOGGER.error('Exception receiving from WebSocket: {}'.format(ex)) + + self._events_server() + + + def _events_server(self): + all_hosts = "0.0.0.0" + + try: + server = serve(self._event_received, all_hosts, int(OWN_PORT)) + except Exception as ex: + LOGGER.error('Error starting server on {}:{}'.format(all_hosts, OWN_PORT)) + LOGGER.error('Exception!: {}'.format(ex)) + else: + with server: + LOGGER.info("Running events server...: {}:{}".format(all_hosts, OWN_PORT)) + server.serve_forever() + + + def _event_received(self, connection): + LOGGER.info("EVENT received!") + for message in connection: + message_json = json.loads(message) + # LOGGER.info("message_json: {}".format(message_json)) + + # Link creation + if 'link_id' in message_json: + link = Link(**message_json) + + service = Service() + service.service_id.service_uuid.uuid = link.link_id.link_uuid.uuid + service.service_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME + service.service_type = ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY + service.service_status.service_status = ServiceStatusEnum.SERVICESTATUS_PLANNED + service_client.CreateService(service) + + links = context_client.ListLinks(Empty()).links + a_device_uuid = device_get_uuid(link.link_endpoint_ids[0].device_id) + a_endpoint_uuid = endpoint_get_uuid(link.link_endpoint_ids[0])[2] + z_device_uuid = device_get_uuid(link.link_endpoint_ids[1].device_id) + z_endpoint_uuid = endpoint_get_uuid(link.link_endpoint_ids[1])[2] + + for _link in links: + for _endpoint_id in _link.link_endpoint_ids: + if _endpoint_id.device_id.device_uuid.uuid == a_device_uuid and \ + _endpoint_id.endpoint_uuid.uuid == a_endpoint_uuid: + a_ep_id = _endpoint_id + elif _endpoint_id.device_id.device_uuid.uuid == z_device_uuid and \ + _endpoint_id.endpoint_uuid.uuid == z_endpoint_uuid: + z_ep_id = _endpoint_id + + if (not 'a_ep_id' in locals()) or (not 'z_ep_id' in locals()): + error_msg = 'Could not get VNT link endpoints' + LOGGER.error(error_msg) + connection.send(error_msg) + return + + service.service_endpoint_ids.append(copy.deepcopy(a_ep_id)) + service.service_endpoint_ids.append(copy.deepcopy(z_ep_id)) + + # service_client.UpdateService(service) + connection.send(grpc_message_to_json_string(link)) + # Link removal + elif 'link_uuid' in message_json: + LOGGER.info('REMOVING VIRTUAL LINK') + link_id = LinkId(**message_json) + + service_id = ServiceId() + service_id.service_uuid.uuid = link_id.link_uuid.uuid + service_id.context_id.context_uuid.uuid = DEFAULT_CONTEXT_NAME + # service_client.DeleteService(service_id) + connection.send(grpc_message_to_json_string(link_id)) + context_client.RemoveLink(link_id) + # Topology received + else: + LOGGER.info('TOPOLOGY') + topology_details = TopologyDetails(**message_json) + + context = Context() + context.context_id.context_uuid.uuid = topology_details.topology_id.context_id.context_uuid.uuid + context_client.SetContext(context) + + topology = Topology() + topology.topology_id.context_id.CopyFrom(context.context_id) + topology.topology_id.topology_uuid.uuid = topology_details.topology_id.topology_uuid.uuid + context_client.SetTopology(topology) + + for device in topology_details.devices: + context_client.SetDevice(device) + + for link in topology_details.links: + context_client.SetLink(link) + class E2EOrchestratorServiceServicerImpl(E2EOrchestratorServiceServicer): def __init__(self): LOGGER.debug("Creating Servicer...") - LOGGER.debug("Servicer Created") + try: + LOGGER.debug("Requesting subscription") + sub_server = SubscriptionServer() + sub_server.start() + LOGGER.debug("Servicer Created") + + except Exception as ex: + LOGGER.info("Exception!: {}".format(ex)) + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) def Compute(self, request: E2EOrchestratorRequest, context: grpc.ServicerContext) -> E2EOrchestratorReply: endpoints_ids = [] diff --git a/src/e2e_orchestrator/service/__main__.py b/src/e2e_orchestrator/service/__main__.py index 5f20fd72f062127e12fc41352e7213fa320d4a94..b4763627d6de04e49765c047d560cc5626fbc17f 100644 --- a/src/e2e_orchestrator/service/__main__.py +++ b/src/e2e_orchestrator/service/__main__.py @@ -43,13 +43,6 @@ def main(): logging.basicConfig(level=log_level) LOGGER = logging.getLogger(__name__) - wait_for_environment_variables( - [ - get_env_var_name(ServiceNameEnum.E2EORCHESTRATOR, ENVVAR_SUFIX_SERVICE_HOST), - get_env_var_name(ServiceNameEnum.E2EORCHESTRATOR, ENVVAR_SUFIX_SERVICE_PORT_GRPC), - ] - ) - signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) diff --git a/src/nbi/Dockerfile b/src/nbi/Dockerfile index c5fc8d32491d7576e93534c75c264e79c37a36c6..ec1c054858b5f97dcdff36e2ccd4c8039942b51e 100644 --- a/src/nbi/Dockerfile +++ b/src/nbi/Dockerfile @@ -16,9 +16,24 @@ FROM python:3.9-slim # Install dependencies RUN apt-get --yes --quiet --quiet update && \ - apt-get --yes --quiet --quiet install wget g++ git && \ + apt-get --yes --quiet --quiet install wget g++ git build-essential cmake libpcre2-dev python3-dev python3-cffi && \ rm -rf /var/lib/apt/lists/* +# Download, build and install libyang. Note that APT package is outdated +# - Ref: https://github.com/CESNET/libyang +# - Ref: https://github.com/CESNET/libyang-python/ +RUN mkdir -p /var/libyang +RUN git clone https://github.com/CESNET/libyang.git /var/libyang +WORKDIR /var/libyang +RUN git fetch +RUN git checkout v2.1.148 +RUN mkdir -p /var/libyang/build +WORKDIR /var/libyang/build +RUN cmake -D CMAKE_BUILD_TYPE:String="Release" .. +RUN make +RUN make install +RUN ldconfig + # Set Python to show logs as they occur ENV PYTHONUNBUFFERED=0 @@ -53,24 +68,6 @@ RUN python3 -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. *.proto RUN rm *.proto RUN find . -type f -exec sed -i -E 's/(import\ .*)_pb2/from . \1_pb2/g' {} \; -# Download, build and install libyang. Note that APT package is outdated -# - Ref: https://github.com/CESNET/libyang -# - Ref: https://github.com/CESNET/libyang-python/ -RUN apt-get --yes --quiet --quiet update && \ - apt-get --yes --quiet --quiet install build-essential cmake libpcre2-dev python3-dev python3-cffi && \ - rm -rf /var/lib/apt/lists/* -RUN mkdir -p /var/libyang -RUN git clone https://github.com/CESNET/libyang.git /var/libyang -WORKDIR /var/libyang -RUN git fetch -RUN git checkout v2.1.148 -RUN mkdir -p /var/libyang/build -WORKDIR /var/libyang/build -RUN cmake -D CMAKE_BUILD_TYPE:String="Release" .. -RUN make -RUN make install -RUN ldconfig - # Create component sub-folders, get specific Python packages RUN mkdir -p /var/teraflow/nbi WORKDIR /var/teraflow/nbi @@ -91,6 +88,8 @@ COPY src/slice/__init__.py slice/__init__.py COPY src/slice/client/. slice/client/ COPY src/qkd_app/__init__.py qkd_app/__init__.py COPY src/qkd_app/client/. qkd_app/client/ +COPY src/vnt_manager/__init__.py vnt_manager/__init__.py +COPY src/vnt_manager/client/. vnt_manager/client/ RUN mkdir -p /var/teraflow/tests/tools COPY src/tests/tools/mock_osm/. tests/tools/mock_osm/ diff --git a/src/nbi/requirements.in b/src/nbi/requirements.in index 4c5460a8e2b3c05d994bbaba4bd2939e629db1e2..f0eec63568d900c1324f74d5c9265b614e2ac7d0 100644 --- a/src/nbi/requirements.in +++ b/src/nbi/requirements.in @@ -25,3 +25,4 @@ git+https://github.com/robshakir/pyangbind.git pydantic==2.6.3 requests==2.27.1 werkzeug==2.3.7 +websockets==12.0 diff --git a/src/nbi/service/NbiServiceServicerImpl.py b/src/nbi/service/NbiServiceServicerImpl.py index b1d62afb11709d9ee237bc1f840b803674923c00..79d8b8ca0074fe63cde141d41a3e6fde475415ee 100644 --- a/src/nbi/service/NbiServiceServicerImpl.py +++ b/src/nbi/service/NbiServiceServicerImpl.py @@ -20,7 +20,7 @@ from common.proto.nbi_pb2_grpc import NbiServiceServicer LOGGER = logging.getLogger(__name__) -METRICS_POOL = MetricsPool('Compute', 'RPC') +METRICS_POOL = MetricsPool('NBI', 'RPC') class NbiServiceServicerImpl(NbiServiceServicer): def __init__(self): diff --git a/src/nbi/service/__main__.py b/src/nbi/service/__main__.py index ddbf7bb8fdeb688cc03b40499e61c5a34f2b82e5..fb735f8a775e8cce1bc696ed4f148b2ab0ec9dcc 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 .context_subscription import register_context_subscription terminate = threading.Event() LOGGER = None @@ -80,6 +81,8 @@ def main(): register_tfs_api(rest_server) rest_server.start() + register_context_subscription() + LOGGER.debug('Configured Resources:') for resource in rest_server.api.resources: LOGGER.debug(' - {:s}'.format(str(resource))) diff --git a/src/nbi/service/context_subscription/__init__.py b/src/nbi/service/context_subscription/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f0d8d7d4229b30d05a1c960d32fb337653be3706 --- /dev/null +++ b/src/nbi/service/context_subscription/__init__.py @@ -0,0 +1,64 @@ +# 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 websockets.sync.server import serve +from common.proto.vnt_manager_pb2 import VNTSubscriptionRequest +from common.Settings import get_setting +from context.client.ContextClient import ContextClient +from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME +from common.tools.object_factory.Topology import json_topology_id +from common.tools.object_factory.Context import json_context_id +from common.proto.context_pb2 import ContextId, TopologyId +import json +import os +from vnt_manager.client.VNTManagerClient import VNTManagerClient + +JSON_ADMIN_CONTEXT_ID = json_context_id(DEFAULT_CONTEXT_NAME) +ADMIN_CONTEXT_ID = ContextId(**JSON_ADMIN_CONTEXT_ID) +ADMIN_TOPOLOGY_ID = TopologyId(**json_topology_id(DEFAULT_TOPOLOGY_NAME, context_id=JSON_ADMIN_CONTEXT_ID)) + +vnt_manager_client: VNTManagerClient = VNTManagerClient() +context_client: ContextClient = ContextClient() + +ALL_HOSTS = "0.0.0.0" +WS_E2E_PORT = str(get_setting('WS_E2E_PORT')) + +LOGGER = logging.getLogger(__name__) + + +def register_context_subscription(): + with serve(subcript_to_vnt_manager, ALL_HOSTS, WS_E2E_PORT, logger=LOGGER) as server: + LOGGER.info("Running subscription server...: {}:{}".format(ALL_HOSTS, str(WS_E2E_PORT))) + server.serve_forever() + LOGGER.info("Exiting subscription server...") + + +def subcript_to_vnt_manager(websocket): + for message in websocket: + LOGGER.debug("Message received: {}".format(message)) + message_json = json.loads(message) + request = VNTSubscriptionRequest() + request.host = message_json['host'] + request.port = message_json['port'] + LOGGER.debug("Received gRPC from ws: {}".format(request)) + + try: + vntm_reply = vnt_manager_client.VNTSubscript(request) + LOGGER.debug("Received gRPC from vntm: {}".format(vntm_reply)) + except Exception as e: + LOGGER.error('Could not subscript to VTNManager: {}'.format(e)) + + websocket.send(vntm_reply.subscription) diff --git a/src/nbi/service/rest_server/nbi_plugins/tfs_api/Resources.py b/src/nbi/service/rest_server/nbi_plugins/tfs_api/Resources.py index f360e318127706b4b4c8fdc4130dfdfc0ba711c0..28f94887a1aa9895a337baa40b3896e3d7e95dc1 100644 --- a/src/nbi/service/rest_server/nbi_plugins/tfs_api/Resources.py +++ b/src/nbi/service/rest_server/nbi_plugins/tfs_api/Resources.py @@ -13,27 +13,34 @@ # limitations under the License. import json +import logging from flask.json import jsonify from flask_restful import Resource, request from werkzeug.exceptions import BadRequest -from common.proto.context_pb2 import Empty +from common.proto.context_pb2 import Empty, LinkTypeEnum from common.tools.grpc.Tools import grpc_message_to_json from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from service.client.ServiceClient import ServiceClient from slice.client.SliceClient import SliceClient +from vnt_manager.client.VNTManagerClient import VNTManagerClient + from .Tools import ( format_grpc_to_json, grpc_connection_id, grpc_context, grpc_context_id, grpc_device, grpc_device_id, grpc_link, grpc_link_id, grpc_policy_rule_id, grpc_service_id, grpc_service, grpc_slice, grpc_slice_id, grpc_topology, grpc_topology_id ) +LOGGER = logging.getLogger(__name__) + + class _Resource(Resource): def __init__(self) -> None: super().__init__() self.context_client = ContextClient() self.device_client = DeviceClient() self.service_client = ServiceClient() + self.vntmanager_client = VNTManagerClient() self.slice_client = SliceClient() class ContextIds(_Resource): @@ -292,9 +299,14 @@ class Link(_Resource): return format_grpc_to_json(self.context_client.GetLink(grpc_link_id(link_uuid))) def put(self, link_uuid : str): - link = request.get_json() - if link_uuid != link['link_id']['link_uuid']['uuid']: + link_json = request.get_json() + link = grpc_link(link_json) + virtual_types = {LinkTypeEnum.LINKTYPE_VIRTUAL_COPPER, LinkTypeEnum.LINKTYPE_VIRTUAL_OPTICAL} + if link_uuid != link.link_id.link_uuid.uuid: raise BadRequest('Mismatching link_uuid') + elif link.link_type in virtual_types: + link = grpc_link(link_json) + return format_grpc_to_json(self.vntmanager_client.SetVirtualLink(link)) return format_grpc_to_json(self.context_client.SetLink(grpc_link(link))) def delete(self, link_uuid : str): diff --git a/src/telemetry/backend/service/TelemetryBackendService.py b/src/telemetry/backend/service/TelemetryBackendService.py index 79a35d343860d19992518c0e8b29e427e5cbbef4..81ef24481cffc70c6b33bbfbf19d57b062729891 100755 --- a/src/telemetry/backend/service/TelemetryBackendService.py +++ b/src/telemetry/backend/service/TelemetryBackendService.py @@ -106,7 +106,7 @@ class TelemetryBackendService(GenericGrpcService): Method receives collector request and initiates collecter backend. """ # print("Initiating backend for collector: ", collector_id) - LOGGER.info("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 @@ -165,9 +165,9 @@ class TelemetryBackendService(GenericGrpcService): Args: err (KafkaError): Kafka error object. msg (Message): Kafka message object. """ - if err: - LOGGER.debug('Message delivery failed: {:}'.format(err)) + if err: + LOGGER.error('Message delivery failed: {:}'.format(err)) # print(f'Message delivery failed: {err}') - else: - LOGGER.info('Message delivered to topic {:}'.format(msg.topic())) - # print(f'Message delivered to topic {msg.topic()}') + #else: + # LOGGER.debug('Message delivered to topic {:}'.format(msg.topic())) + # # print(f'Message delivered to topic {msg.topic()}') diff --git a/src/telemetry/frontend/client/TelemetryFrontendClient.py b/src/telemetry/frontend/client/TelemetryFrontendClient.py index cd36ecd45933ad10758e408cf03c1bf834d27ba6..afcf241530a41f1f4ab1729379a4e5196c25d04f 100644 --- a/src/telemetry/frontend/client/TelemetryFrontendClient.py +++ b/src/telemetry/frontend/client/TelemetryFrontendClient.py @@ -29,8 +29,8 @@ RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, class TelemetryFrontendClient: def __init__(self, host=None, port=None): - if not host: host = get_service_host(ServiceNameEnum.TELEMETRYFRONTEND) - if not port: port = get_service_port_grpc(ServiceNameEnum.TELEMETRYFRONTEND) + if not host: host = get_service_host(ServiceNameEnum.TELEMETRY) + if not port: port = get_service_port_grpc(ServiceNameEnum.TELEMETRY) self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) LOGGER.debug('Creating channel to {:s}...'.format(str(self.endpoint))) self.channel = None diff --git a/src/telemetry/frontend/service/TelemetryFrontendService.py b/src/telemetry/frontend/service/TelemetryFrontendService.py index abd361aa0082e2de1d1f5fa7e81a336f3091af9a..49def20a1ce3cee1062d1e582fd8ec28308652b7 100644 --- a/src/telemetry/frontend/service/TelemetryFrontendService.py +++ b/src/telemetry/frontend/service/TelemetryFrontendService.py @@ -21,7 +21,7 @@ from telemetry.frontend.service.TelemetryFrontendServiceServicerImpl import Tele class TelemetryFrontendService(GenericGrpcService): def __init__(self, cls_name: str = __name__) -> None: - port = get_service_port_grpc(ServiceNameEnum.TELEMETRYFRONTEND) + port = get_service_port_grpc(ServiceNameEnum.TELEMETRY) super().__init__(port, cls_name=cls_name) self.telemetry_frontend_servicer = TelemetryFrontendServiceServicerImpl() diff --git a/src/telemetry/frontend/tests/test_frontend.py b/src/telemetry/frontend/tests/test_frontend.py index c3f8091c83f56fd4a134ec092b1e22723040595d..988d76af0380302cd6351d46eccf6159bf1dc5ab 100644 --- a/src/telemetry/frontend/tests/test_frontend.py +++ b/src/telemetry/frontend/tests/test_frontend.py @@ -36,9 +36,9 @@ from telemetry.frontend.service.TelemetryFrontendServiceServicerImpl import Tele LOCAL_HOST = '127.0.0.1' -TELEMETRY_FRONTEND_PORT = str(get_service_port_grpc(ServiceNameEnum.TELEMETRYFRONTEND)) -os.environ[get_env_var_name(ServiceNameEnum.TELEMETRYFRONTEND, ENVVAR_SUFIX_SERVICE_HOST )] = str(LOCAL_HOST) -os.environ[get_env_var_name(ServiceNameEnum.TELEMETRYFRONTEND, ENVVAR_SUFIX_SERVICE_PORT_GRPC)] = str(TELEMETRY_FRONTEND_PORT) +TELEMETRY_FRONTEND_PORT = str(get_service_port_grpc(ServiceNameEnum.TELEMETRY)) +os.environ[get_env_var_name(ServiceNameEnum.TELEMETRY, ENVVAR_SUFIX_SERVICE_HOST )] = str(LOCAL_HOST) +os.environ[get_env_var_name(ServiceNameEnum.TELEMETRY, ENVVAR_SUFIX_SERVICE_PORT_GRPC)] = str(TELEMETRY_FRONTEND_PORT) LOGGER = logging.getLogger(__name__) diff --git a/src/tests/.gitlab-ci.yml b/src/tests/.gitlab-ci.yml index ed4bad5f60117a061d3879cd0b2590b794a06eac..de369a3be1e814f77e1ab20575248e5cb69c3c4a 100644 --- a/src/tests/.gitlab-ci.yml +++ b/src/tests/.gitlab-ci.yml @@ -14,10 +14,11 @@ # include the individual .gitlab-ci.yml of each end-to-end integration test include: - - local: '/src/tests/eucnc24/.gitlab-ci.yml' #- local: '/src/tests/ofc22/.gitlab-ci.yml' #- local: '/src/tests/oeccpsc22/.gitlab-ci.yml' #- local: '/src/tests/ecoc22/.gitlab-ci.yml' #- local: '/src/tests/nfvsdn22/.gitlab-ci.yml' #- local: '/src/tests/ofc23/.gitlab-ci.yml' #- local: '/src/tests/ofc24/.gitlab-ci.yml' + - local: '/src/tests/eucnc24/.gitlab-ci.yml' + #- local: '/src/tests/ecoc24/.gitlab-ci.yml' diff --git a/src/tests/ecoc24/__init__.py b/src/tests/ecoc24/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3ee6f7071f145e06c3aeaefc09a43ccd88e619e3 --- /dev/null +++ b/src/tests/ecoc24/__init__.py @@ -0,0 +1,14 @@ +# 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/tests/ecoc24/deploy_e2e.sh b/src/tests/ecoc24/deploy_e2e.sh new file mode 100755 index 0000000000000000000000000000000000000000..23ff8b7f037b8f2497e6ef851c3aa9f301772e1a --- /dev/null +++ b/src/tests/ecoc24/deploy_e2e.sh @@ -0,0 +1,33 @@ +#!/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. + + +# Delete old namespaces +kubectl delete namespace tfs-e2e + +# Delete secondary ingress controllers +kubectl delete -f src/tests/ecoc24/nginx-ingress-controller-e2e.yaml + +# Create secondary ingress controllers +kubectl apply -f src/tests/ecoc24/nginx-ingress-controller-e2e.yaml + +# Deploy TFS for E2E +source src/tests/ecoc24/deploy_specs_e2e.sh +./deploy/all.sh + +#Configure Subscription WS +./src/tests/ecoc24/deploy/subscription_ws_e2e.sh + +mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_e2e.sh diff --git a/src/tests/ecoc24/deploy_ip.sh b/src/tests/ecoc24/deploy_ip.sh new file mode 100755 index 0000000000000000000000000000000000000000..a6c5e82557d02c89f67bbbf0ee0a6f88650ba9d1 --- /dev/null +++ b/src/tests/ecoc24/deploy_ip.sh @@ -0,0 +1,33 @@ +#!/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. + + +# Delete old namespaces +kubectl delete namespace tfs-ip + +# Delete secondary ingress controllers +kubectl delete -f src/tests/ecoc24/nginx-ingress-controller-ip.yaml + +# Create secondary ingress controllers +kubectl apply -f src/tests/ecoc24/nginx-ingress-controller-ip.yaml + +# Deploy TFS for IP +source src/tests/ecoc24/deploy_specs_ip.sh +./deploy/all.sh + +#Configure Subscription WS +./src/tests/ecoc24/subscription_ws_ip.sh + +mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_ip.sh diff --git a/src/tests/ecoc24/deploy_opt.sh b/src/tests/ecoc24/deploy_opt.sh new file mode 100755 index 0000000000000000000000000000000000000000..3a9523768ec21c2e177a3567a51cdc94f4db992b --- /dev/null +++ b/src/tests/ecoc24/deploy_opt.sh @@ -0,0 +1,29 @@ +#!/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. + + +# Delete old namespaces +kubectl delete namespace tfs-opt + +# Delete secondary ingress controllers +kubectl delete -f src/tests/ecoc24/nginx-ingress-controller-opt.yaml + +# Create secondary ingress controllers +kubectl apply -f src/tests/ecoc24/nginx-ingress-controller-opt.yaml + +# Deploy TFS for OPT +source src/tests/ecoc24/deploy_specs_opt.sh +./deploy/all.sh +mv tfs_runtime_env_vars.sh tfs_runtime_env_vars_opt.sh diff --git a/src/tests/ecoc24/deploy_specs_e2e.sh b/src/tests/ecoc24/deploy_specs_e2e.sh new file mode 100755 index 0000000000000000000000000000000000000000..e1e358cfcebe6d079f175e10a67bd097bf46dd7e --- /dev/null +++ b/src/tests/ecoc24/deploy_specs_e2e.sh @@ -0,0 +1,216 @@ +#!/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. + + +# ----- TeraFlowSDN ------------------------------------------------------------ + +# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to. +export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/" + +# Set the list of components, separated by spaces, you want to build images for, and deploy. +export TFS_COMPONENTS="context device pathcomp service slice nbi webui" + +# Uncomment to activate Monitoring (old) +#export TFS_COMPONENTS="${TFS_COMPONENTS} monitoring" + +# Uncomment to activate Monitoring Framework (new) +#export TFS_COMPONENTS="${TFS_COMPONENTS} kpi_manager kpi_value_writer kpi_value_api telemetry analytics automation" + +# Uncomment to activate QoS Profiles +#export TFS_COMPONENTS="${TFS_COMPONENTS} qos_profile" + +# Uncomment to activate BGP-LS Speaker +#export TFS_COMPONENTS="${TFS_COMPONENTS} bgpls_speaker" + +# Uncomment to activate Optical Controller +# To manage optical connections, "service" requires "opticalcontroller" to be deployed +# before "service", thus we "hack" the TFS_COMPONENTS environment variable prepending the +# "opticalcontroller" only if "service" is already in TFS_COMPONENTS, and re-export it. +#if [[ "$TFS_COMPONENTS" == *"service"* ]]; then +# BEFORE="${TFS_COMPONENTS% service*}" +# AFTER="${TFS_COMPONENTS#* service}" +# export TFS_COMPONENTS="${BEFORE} opticalcontroller service ${AFTER}" +#fi + +# Uncomment to activate ZTP +#export TFS_COMPONENTS="${TFS_COMPONENTS} ztp" + +# Uncomment to activate Policy Manager +#export TFS_COMPONENTS="${TFS_COMPONENTS} policy" + +# Uncomment to activate Optical CyberSecurity +#export TFS_COMPONENTS="${TFS_COMPONENTS} dbscanserving opticalattackmitigator opticalattackdetector opticalattackmanager" + +# Uncomment to activate L3 CyberSecurity +#export TFS_COMPONENTS="${TFS_COMPONENTS} l3_attackmitigator l3_centralizedattackdetector" + +# Uncomment to activate TE +#export TFS_COMPONENTS="${TFS_COMPONENTS} te" + +# Uncomment to activate Forecaster +#export TFS_COMPONENTS="${TFS_COMPONENTS} forecaster" + +# Uncomment to activate E2E Orchestrator +export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" + +# Uncomment to activate VNT Manager +# export TFS_COMPONENTS="${TFS_COMPONENTS} vnt_manager" + +# Uncomment to activate DLT and Interdomain +#export TFS_COMPONENTS="${TFS_COMPONENTS} interdomain dlt" +#if [[ "$TFS_COMPONENTS" == *"dlt"* ]]; then +# export KEY_DIRECTORY_PATH="src/dlt/gateway/keys/priv_sk" +# export CERT_DIRECTORY_PATH="src/dlt/gateway/keys/cert.pem" +# export TLS_CERT_PATH="src/dlt/gateway/keys/ca.crt" +#fi + +# Uncomment to activate QKD App +# To manage QKD Apps, "service" requires "qkd_app" to be deployed +# before "service", thus we "hack" the TFS_COMPONENTS environment variable prepending the +# "qkd_app" only if "service" is already in TFS_COMPONENTS, and re-export it. +#if [[ "$TFS_COMPONENTS" == *"service"* ]]; then +# BEFORE="${TFS_COMPONENTS% service*}" +# AFTER="${TFS_COMPONENTS#* service}" +# export TFS_COMPONENTS="${BEFORE} qkd_app service ${AFTER}" +#fi + +# Uncomment to activate Load Generator +#export TFS_COMPONENTS="${TFS_COMPONENTS} load_generator" + + +# Set the tag you want to use for your images. +export TFS_IMAGE_TAG="dev" + +# Set the name of the Kubernetes namespace to deploy TFS to. +export TFS_K8S_NAMESPACE="tfs-e2e" + +# Set additional manifest files to be applied after the deployment +export TFS_EXTRA_MANIFESTS="src/tests/ecoc24/tfs-ingress-e2e.yaml" + +# Uncomment to monitor performance of components +#export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/servicemonitors.yaml" + +# Uncomment when deploying Optical CyberSecurity +#export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/cachingservice.yaml" + +# Set the new Grafana admin password +export TFS_GRAFANA_PASSWORD="admin123+" + +# Disable skip-build flag to rebuild the Docker images. +export TFS_SKIP_BUILD="" + + +# ----- CockroachDB ------------------------------------------------------------ + +# Set the namespace where CockroackDB will be deployed. +export CRDB_NAMESPACE="crdb" + +# Set the external port CockroackDB Postgre SQL interface will be exposed to. +export CRDB_EXT_PORT_SQL="26257" + +# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to. +export CRDB_EXT_PORT_HTTP="8081" + +# Set the database username to be used by Context. +export CRDB_USERNAME="tfs" + +# Set the database user's password to be used by Context. +export CRDB_PASSWORD="tfs123" + +# Set the database name to be used by Context. +export CRDB_DATABASE="tfs_e2e" + +# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing. +# See ./deploy/all.sh or ./deploy/crdb.sh for additional details +export CRDB_DEPLOY_MODE="single" + +# Disable flag for dropping database, if it exists. +export CRDB_DROP_DATABASE_IF_EXISTS="YES" + +# Disable flag for re-deploying CockroachDB from scratch. +export CRDB_REDEPLOY="" + + +# ----- NATS ------------------------------------------------------------------- + +# Set the namespace where NATS will be deployed. +export NATS_NAMESPACE="nats-e2e" + +# Set the external port NATS Client interface will be exposed to. +export NATS_EXT_PORT_CLIENT="4223" + +# Set the external port NATS HTTP Mgmt GUI interface will be exposed to. +export NATS_EXT_PORT_HTTP="8223" + +# Set NATS installation mode to 'single'. This option is convenient for development and testing. +# See ./deploy/all.sh or ./deploy/nats.sh for additional details +export NATS_DEPLOY_MODE="single" + +# Disable flag for re-deploying NATS from scratch. +export NATS_REDEPLOY="" + + +# ----- QuestDB ---------------------------------------------------------------- + +# Set the namespace where QuestDB will be deployed. +export QDB_NAMESPACE="qdb-e2e" + +# Set the external port QuestDB Postgre SQL interface will be exposed to. +export QDB_EXT_PORT_SQL="8813" + +# Set the external port QuestDB Influx Line Protocol interface will be exposed to. +export QDB_EXT_PORT_ILP="9011" + +# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to. +export QDB_EXT_PORT_HTTP="9001" + +# Set the database username to be used for QuestDB. +export QDB_USERNAME="admin" + +# Set the database user's password to be used for QuestDB. +export QDB_PASSWORD="quest" + +# Set the table name to be used by Monitoring for KPIs. +export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis" + +# Set the table name to be used by Slice for plotting groups. +export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups" + +# Disable flag for dropping tables if they exist. +export QDB_DROP_TABLES_IF_EXIST="YES" + +# Disable flag for re-deploying QuestDB from scratch. +export QDB_REDEPLOY="" + + +# ----- K8s Observability ------------------------------------------------------ + +# Set the external port Prometheus Mgmt HTTP GUI interface will be exposed to. +export PROM_EXT_PORT_HTTP="9090" + +# Set the external port Grafana HTTP Dashboards will be exposed to. +export GRAF_EXT_PORT_HTTP="3000" + + +# ----- Apache Kafka ----------------------------------------------------------- + +# Set the namespace where Apache Kafka will be deployed. +export KFK_NAMESPACE="kafka" + +# Set the port Apache Kafka server will be exposed to. +export KFK_SERVER_PORT="9092" + +# Set the flag to YES for redeploying of Apache Kafka +export KFK_REDEPLOY="" diff --git a/src/tests/ecoc24/deploy_specs_ip.sh b/src/tests/ecoc24/deploy_specs_ip.sh new file mode 100755 index 0000000000000000000000000000000000000000..7542a0fb54bacf0273a5b38ca23ca67fae352c0c --- /dev/null +++ b/src/tests/ecoc24/deploy_specs_ip.sh @@ -0,0 +1,216 @@ +#!/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. + + +# ----- TeraFlowSDN ------------------------------------------------------------ + +# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to. +export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/" + +# Set the list of components, separated by spaces, you want to build images for, and deploy. +export TFS_COMPONENTS="context device pathcomp service slice nbi webui" + +# Uncomment to activate Monitoring (old) +#export TFS_COMPONENTS="${TFS_COMPONENTS} monitoring" + +# Uncomment to activate Monitoring Framework (new) +#export TFS_COMPONENTS="${TFS_COMPONENTS} kpi_manager kpi_value_writer kpi_value_api telemetry analytics automation" + +# Uncomment to activate QoS Profiles +#export TFS_COMPONENTS="${TFS_COMPONENTS} qos_profile" + +# Uncomment to activate BGP-LS Speaker +#export TFS_COMPONENTS="${TFS_COMPONENTS} bgpls_speaker" + +# Uncomment to activate Optical Controller +# To manage optical connections, "service" requires "opticalcontroller" to be deployed +# before "service", thus we "hack" the TFS_COMPONENTS environment variable prepending the +# "opticalcontroller" only if "service" is already in TFS_COMPONENTS, and re-export it. +#if [[ "$TFS_COMPONENTS" == *"service"* ]]; then +# BEFORE="${TFS_COMPONENTS% service*}" +# AFTER="${TFS_COMPONENTS#* service}" +# export TFS_COMPONENTS="${BEFORE} opticalcontroller service ${AFTER}" +#fi + +# Uncomment to activate ZTP +#export TFS_COMPONENTS="${TFS_COMPONENTS} ztp" + +# Uncomment to activate Policy Manager +#export TFS_COMPONENTS="${TFS_COMPONENTS} policy" + +# Uncomment to activate Optical CyberSecurity +#export TFS_COMPONENTS="${TFS_COMPONENTS} dbscanserving opticalattackmitigator opticalattackdetector opticalattackmanager" + +# Uncomment to activate L3 CyberSecurity +#export TFS_COMPONENTS="${TFS_COMPONENTS} l3_attackmitigator l3_centralizedattackdetector" + +# Uncomment to activate TE +#export TFS_COMPONENTS="${TFS_COMPONENTS} te" + +# Uncomment to activate Forecaster +#export TFS_COMPONENTS="${TFS_COMPONENTS} forecaster" + +# Uncomment to activate E2E Orchestrator +# export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" + +# Uncomment to activate VNT Manager +export TFS_COMPONENTS="${TFS_COMPONENTS} vnt_manager" + +# Uncomment to activate DLT and Interdomain +#export TFS_COMPONENTS="${TFS_COMPONENTS} interdomain dlt" +#if [[ "$TFS_COMPONENTS" == *"dlt"* ]]; then +# export KEY_DIRECTORY_PATH="src/dlt/gateway/keys/priv_sk" +# export CERT_DIRECTORY_PATH="src/dlt/gateway/keys/cert.pem" +# export TLS_CERT_PATH="src/dlt/gateway/keys/ca.crt" +#fi + +# Uncomment to activate QKD App +# To manage QKD Apps, "service" requires "qkd_app" to be deployed +# before "service", thus we "hack" the TFS_COMPONENTS environment variable prepending the +# "qkd_app" only if "service" is already in TFS_COMPONENTS, and re-export it. +#if [[ "$TFS_COMPONENTS" == *"service"* ]]; then +# BEFORE="${TFS_COMPONENTS% service*}" +# AFTER="${TFS_COMPONENTS#* service}" +# export TFS_COMPONENTS="${BEFORE} qkd_app service ${AFTER}" +#fi + +# Uncomment to activate Load Generator +#export TFS_COMPONENTS="${TFS_COMPONENTS} load_generator" + + +# Set the tag you want to use for your images. +export TFS_IMAGE_TAG="dev" + +# Set the name of the Kubernetes namespace to deploy TFS to. +export TFS_K8S_NAMESPACE="tfs-ip" + +# Set additional manifest files to be applied after the deployment +export TFS_EXTRA_MANIFESTS="src/tests/ecoc24/tfs-ingress-ip.yaml" + +# Uncomment to monitor performance of components +#export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/servicemonitors.yaml" + +# Uncomment when deploying Optical CyberSecurity +#export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/cachingservice.yaml" + +# Set the new Grafana admin password +export TFS_GRAFANA_PASSWORD="admin123+" + +# Disable skip-build flag to rebuild the Docker images. +export TFS_SKIP_BUILD="" + + +# ----- CockroachDB ------------------------------------------------------------ + +# Set the namespace where CockroackDB will be deployed. +export CRDB_NAMESPACE="crdb" + +# Set the external port CockroackDB Postgre SQL interface will be exposed to. +export CRDB_EXT_PORT_SQL="26257" + +# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to. +export CRDB_EXT_PORT_HTTP="8081" + +# Set the database username to be used by Context. +export CRDB_USERNAME="tfs" + +# Set the database user's password to be used by Context. +export CRDB_PASSWORD="tfs123" + +# Set the database name to be used by Context. +export CRDB_DATABASE="tfs_ip" + +# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing. +# See ./deploy/all.sh or ./deploy/crdb.sh for additional details +export CRDB_DEPLOY_MODE="single" + +# Disable flag for dropping database, if it exists. +export CRDB_DROP_DATABASE_IF_EXISTS="YES" + +# Disable flag for re-deploying CockroachDB from scratch. +export CRDB_REDEPLOY="" + + +# ----- NATS ------------------------------------------------------------------- + +# Set the namespace where NATS will be deployed. +export NATS_NAMESPACE="nats-ip" + +# Set the external port NATS Client interface will be exposed to. +export NATS_EXT_PORT_CLIENT="4224" + +# Set the external port NATS HTTP Mgmt GUI interface will be exposed to. +export NATS_EXT_PORT_HTTP="8224" + +# Set NATS installation mode to 'single'. This option is convenient for development and testing. +# See ./deploy/all.sh or ./deploy/nats.sh for additional details +export NATS_DEPLOY_MODE="single" + +# Disable flag for re-deploying NATS from scratch. +export NATS_REDEPLOY="" + + +# ----- QuestDB ---------------------------------------------------------------- + +# Set the namespace where QuestDB will be deployed. +export QDB_NAMESPACE="qdb-ip" + +# Set the external port QuestDB Postgre SQL interface will be exposed to. +export QDB_EXT_PORT_SQL="8814" + +# Set the external port QuestDB Influx Line Protocol interface will be exposed to. +export QDB_EXT_PORT_ILP="9012" + +# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to. +export QDB_EXT_PORT_HTTP="9002" + +# Set the database username to be used for QuestDB. +export QDB_USERNAME="admin" + +# Set the database user's password to be used for QuestDB. +export QDB_PASSWORD="quest" + +# Set the table name to be used by Monitoring for KPIs. +export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis" + +# Set the table name to be used by Slice for plotting groups. +export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups" + +# Disable flag for dropping tables if they exist. +export QDB_DROP_TABLES_IF_EXIST="YES" + +# Disable flag for re-deploying QuestDB from scratch. +export QDB_REDEPLOY="" + + +# ----- K8s Observability ------------------------------------------------------ + +# Set the external port Prometheus Mgmt HTTP GUI interface will be exposed to. +export PROM_EXT_PORT_HTTP="9090" + +# Set the external port Grafana HTTP Dashboards will be exposed to. +export GRAF_EXT_PORT_HTTP="3000" + + +# ----- Apache Kafka ----------------------------------------------------------- + +# Set the namespace where Apache Kafka will be deployed. +export KFK_NAMESPACE="kafka" + +# Set the port Apache Kafka server will be exposed to. +export KFK_SERVER_PORT="9092" + +# Set the flag to YES for redeploying of Apache Kafka +export KFK_REDEPLOY="" diff --git a/src/tests/ecoc24/deploy_specs_opt.sh b/src/tests/ecoc24/deploy_specs_opt.sh new file mode 100755 index 0000000000000000000000000000000000000000..5d258e60fdb632c692bd1a0ba6ab911e98ae7dc4 --- /dev/null +++ b/src/tests/ecoc24/deploy_specs_opt.sh @@ -0,0 +1,216 @@ +#!/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. + + +# ----- TeraFlowSDN ------------------------------------------------------------ + +# Set the URL of the internal MicroK8s Docker registry where the images will be uploaded to. +export TFS_REGISTRY_IMAGES="http://localhost:32000/tfs/" + +# Set the list of components, separated by spaces, you want to build images for, and deploy. +export TFS_COMPONENTS="context device pathcomp service slice nbi webui" + +# Uncomment to activate Monitoring (old) +#export TFS_COMPONENTS="${TFS_COMPONENTS} monitoring" + +# Uncomment to activate Monitoring Framework (new) +#export TFS_COMPONENTS="${TFS_COMPONENTS} kpi_manager kpi_value_writer kpi_value_api telemetry analytics automation" + +# Uncomment to activate QoS Profiles +#export TFS_COMPONENTS="${TFS_COMPONENTS} qos_profile" + +# Uncomment to activate BGP-LS Speaker +#export TFS_COMPONENTS="${TFS_COMPONENTS} bgpls_speaker" + +# Uncomment to activate Optical Controller +# To manage optical connections, "service" requires "opticalcontroller" to be deployed +# before "service", thus we "hack" the TFS_COMPONENTS environment variable prepending the +# "opticalcontroller" only if "service" is already in TFS_COMPONENTS, and re-export it. +#if [[ "$TFS_COMPONENTS" == *"service"* ]]; then +# BEFORE="${TFS_COMPONENTS% service*}" +# AFTER="${TFS_COMPONENTS#* service}" +# export TFS_COMPONENTS="${BEFORE} opticalcontroller service ${AFTER}" +#fi + +# Uncomment to activate ZTP +#export TFS_COMPONENTS="${TFS_COMPONENTS} ztp" + +# Uncomment to activate Policy Manager +#export TFS_COMPONENTS="${TFS_COMPONENTS} policy" + +# Uncomment to activate Optical CyberSecurity +#export TFS_COMPONENTS="${TFS_COMPONENTS} dbscanserving opticalattackmitigator opticalattackdetector opticalattackmanager" + +# Uncomment to activate L3 CyberSecurity +#export TFS_COMPONENTS="${TFS_COMPONENTS} l3_attackmitigator l3_centralizedattackdetector" + +# Uncomment to activate TE +#export TFS_COMPONENTS="${TFS_COMPONENTS} te" + +# Uncomment to activate Forecaster +#export TFS_COMPONENTS="${TFS_COMPONENTS} forecaster" + +# Uncomment to activate E2E Orchestrator +# export TFS_COMPONENTS="${TFS_COMPONENTS} e2e_orchestrator" + +# Uncomment to activate VNT Manager +# export TFS_COMPONENTS="${TFS_COMPONENTS} vnt_manager" + +# Uncomment to activate DLT and Interdomain +#export TFS_COMPONENTS="${TFS_COMPONENTS} interdomain dlt" +#if [[ "$TFS_COMPONENTS" == *"dlt"* ]]; then +# export KEY_DIRECTORY_PATH="src/dlt/gateway/keys/priv_sk" +# export CERT_DIRECTORY_PATH="src/dlt/gateway/keys/cert.pem" +# export TLS_CERT_PATH="src/dlt/gateway/keys/ca.crt" +#fi + +# Uncomment to activate QKD App +# To manage QKD Apps, "service" requires "qkd_app" to be deployed +# before "service", thus we "hack" the TFS_COMPONENTS environment variable prepending the +# "qkd_app" only if "service" is already in TFS_COMPONENTS, and re-export it. +#if [[ "$TFS_COMPONENTS" == *"service"* ]]; then +# BEFORE="${TFS_COMPONENTS% service*}" +# AFTER="${TFS_COMPONENTS#* service}" +# export TFS_COMPONENTS="${BEFORE} qkd_app service ${AFTER}" +#fi + +# Uncomment to activate Load Generator +#export TFS_COMPONENTS="${TFS_COMPONENTS} load_generator" + + +# Set the tag you want to use for your images. +export TFS_IMAGE_TAG="dev" + +# Set the name of the Kubernetes namespace to deploy TFS to. +export TFS_K8S_NAMESPACE="tfs-opt" + +# Set additional manifest files to be applied after the deployment +export TFS_EXTRA_MANIFESTS="src/tests/ecoc24/tfs-ingress-opt.yaml" + +# Uncomment to monitor performance of components +#export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/servicemonitors.yaml" + +# Uncomment when deploying Optical CyberSecurity +#export TFS_EXTRA_MANIFESTS="${TFS_EXTRA_MANIFESTS} manifests/cachingservice.yaml" + +# Set the new Grafana admin password +export TFS_GRAFANA_PASSWORD="admin123+" + +# Disable skip-build flag to rebuild the Docker images. +export TFS_SKIP_BUILD="" + + +# ----- CockroachDB ------------------------------------------------------------ + +# Set the namespace where CockroackDB will be deployed. +export CRDB_NAMESPACE="crdb" + +# Set the external port CockroackDB Postgre SQL interface will be exposed to. +export CRDB_EXT_PORT_SQL="26257" + +# Set the external port CockroackDB HTTP Mgmt GUI interface will be exposed to. +export CRDB_EXT_PORT_HTTP="8081" + +# Set the database username to be used by Context. +export CRDB_USERNAME="tfs" + +# Set the database user's password to be used by Context. +export CRDB_PASSWORD="tfs123" + +# Set the database name to be used by Context. +export CRDB_DATABASE="tfs_ip" + +# Set CockroachDB installation mode to 'single'. This option is convenient for development and testing. +# See ./deploy/all.sh or ./deploy/crdb.sh for additional details +export CRDB_DEPLOY_MODE="single" + +# Disable flag for dropping database, if it exists. +export CRDB_DROP_DATABASE_IF_EXISTS="YES" + +# Disable flag for re-deploying CockroachDB from scratch. +export CRDB_REDEPLOY="" + + +# ----- NATS ------------------------------------------------------------------- + +# Set the namespace where NATS will be deployed. +export NATS_NAMESPACE="nats-opt" + +# Set the external port NATS Client interface will be exposed to. +export NATS_EXT_PORT_CLIENT="4224" + +# Set the external port NATS HTTP Mgmt GUI interface will be exposed to. +export NATS_EXT_PORT_HTTP="8224" + +# Set NATS installation mode to 'single'. This option is convenient for development and testing. +# See ./deploy/all.sh or ./deploy/nats.sh for additional details +export NATS_DEPLOY_MODE="single" + +# Disable flag for re-deploying NATS from scratch. +export NATS_REDEPLOY="" + + +# ----- QuestDB ---------------------------------------------------------------- + +# Set the namespace where QuestDB will be deployed. +export QDB_NAMESPACE="qdb-opt" + +# Set the external port QuestDB Postgre SQL interface will be exposed to. +export QDB_EXT_PORT_SQL="8814" + +# Set the external port QuestDB Influx Line Protocol interface will be exposed to. +export QDB_EXT_PORT_ILP="9012" + +# Set the external port QuestDB HTTP Mgmt GUI interface will be exposed to. +export QDB_EXT_PORT_HTTP="9002" + +# Set the database username to be used for QuestDB. +export QDB_USERNAME="admin" + +# Set the database user's password to be used for QuestDB. +export QDB_PASSWORD="quest" + +# Set the table name to be used by Monitoring for KPIs. +export QDB_TABLE_MONITORING_KPIS="tfs_monitoring_kpis" + +# Set the table name to be used by Slice for plotting groups. +export QDB_TABLE_SLICE_GROUPS="tfs_slice_groups" + +# Disable flag for dropping tables if they exist. +export QDB_DROP_TABLES_IF_EXIST="YES" + +# Disable flag for re-deploying QuestDB from scratch. +export QDB_REDEPLOY="" + + +# ----- K8s Observability ------------------------------------------------------ + +# Set the external port Prometheus Mgmt HTTP GUI interface will be exposed to. +export PROM_EXT_PORT_HTTP="9090" + +# Set the external port Grafana HTTP Dashboards will be exposed to. +export GRAF_EXT_PORT_HTTP="3000" + + +# ----- Apache Kafka ----------------------------------------------------------- + +# Set the namespace where Apache Kafka will be deployed. +export KFK_NAMESPACE="kafka" + +# Set the port Apache Kafka server will be exposed to. +export KFK_SERVER_PORT="9092" + +# Set the flag to YES for redeploying of Apache Kafka +export KFK_REDEPLOY="" diff --git a/src/tests/ecoc24/descriptors/emulated/dc-2-dc-service.json b/src/tests/ecoc24/descriptors/emulated/dc-2-dc-service.json new file mode 100644 index 0000000000000000000000000000000000000000..44c80ad46f727cbe9af7fd78f73184e756d32a92 --- /dev/null +++ b/src/tests/ecoc24/descriptors/emulated/dc-2-dc-service.json @@ -0,0 +1,48 @@ +{ + "services": [ + { + "service_id": { + "context_id": { + "context_uuid": { + "uuid": "admin" + } + }, + "service_uuid": { + "uuid": "dc-2-dc-svc" + } + }, + "service_type": 2, + "service_status": {"service_status": 1}, + "service_endpoint_ids": [ + {"device_id":{"device_uuid":{"uuid":"DC1"}},"endpoint_uuid":{"uuid":"int"}}, + {"device_id":{"device_uuid":{"uuid":"DC2"}},"endpoint_uuid":{"uuid":"int"}} + ], + "service_constraints": [ + {"sla_capacity": {"capacity_gbps": 10.0}}, + {"sla_latency": {"e2e_latency_ms": 15.2}} + ], + "service_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "/settings", "resource_value": { + "address_families": ["IPV4"], "bgp_as": 65000, "bgp_route_target": "65000:123", + "mtu": 1512, "vlan_id": 300 + }}}, + {"action": 1, "custom": {"resource_key": "/device[PE1]/endpoint[1/1]/settings", "resource_value": { + "route_distinguisher": "65000:123", "router_id": "10.0.0.1", + "address_ip": "3.3.1.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300 + }}}, + {"action": 1, "custom": {"resource_key": "/device[PE2]/endpoint[1/1]/settings", "resource_value": { + "route_distinguisher": "65000:123", "router_id": "10.0.0.2", + "address_ip": "3.3.2.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300 + }}}, + {"action": 1, "custom": {"resource_key": "/device[PE3]/endpoint[1/1]/settings", "resource_value": { + "route_distinguisher": "65000:123", "router_id": "10.0.0.3", + "address_ip": "3.3.3.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300 + }}}, + {"action": 1, "custom": {"resource_key": "/device[PE4]/endpoint[1/1]/settings", "resource_value": { + "route_distinguisher": "65000:123", "router_id": "10.0.0.4", + "address_ip": "3.3.4.1", "address_prefix": 24, "sub_interface_index": 1, "vlan_id": 300 + }}} + ]} + } + ] +} diff --git a/src/tests/ecoc24/descriptors/emulated/descriptor_ip.json b/src/tests/ecoc24/descriptors/emulated/descriptor_ip.json new file mode 100644 index 0000000000000000000000000000000000000000..516a8bdebba9b8a7dcdc0a245d52dd17b65cce07 --- /dev/null +++ b/src/tests/ecoc24/descriptors/emulated/descriptor_ip.json @@ -0,0 +1,34 @@ +{ + "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": "IP1"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 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": [ + {"sample_types": [], "type": "copper/internal", "uuid": "CTP1"}, + {"sample_types": [], "type": "copper/internal", "uuid": "CTP2"}, + {"sample_types": [], "type": "copper/internal", "uuid": "CTP3"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "IP2"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 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": [ + {"sample_types": [], "type": "copper/internal", "uuid": "CTP1"}, + {"sample_types": [], "type": "copper/internal", "uuid": "CTP2"}, + {"sample_types": [], "type": "copper/internal", "uuid": "CTP3"} + ]}}} + ]} + } + ] +} diff --git a/src/tests/ecoc24/descriptors/emulated/link_mapping.json b/src/tests/ecoc24/descriptors/emulated/link_mapping.json new file mode 100644 index 0000000000000000000000000000000000000000..9ac5a25994c867bb5387e00363e7b3e0908e030d --- /dev/null +++ b/src/tests/ecoc24/descriptors/emulated/link_mapping.json @@ -0,0 +1,34 @@ +{ + "contexts": [ + {"context_id": {"context_uuid": {"uuid": "admin"}}} + ], + "topologies": [ + {"topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}} + ], + "links": [ + {"link_id": {"link_uuid": {"uuid": "IP1_CTP1-MGON1_OTP1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "IP1"}}, "endpoint_uuid": {"uuid": "CTP1"}}, + {"device_id": {"device_uuid": {"uuid": "MG-ON1"}}, "endpoint_uuid": {"uuid": "OTP1"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "IP1_CTP2-MGON1_OTP2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "IP1"}}, "endpoint_uuid": {"uuid": "CTP2"}}, + {"device_id": {"device_uuid": {"uuid": "MG-ON1"}}, "endpoint_uuid": {"uuid": "OTP2"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "IP1CTP3-MGON1_OTP3"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "IP1"}}, "endpoint_uuid": {"uuid": "CTP3"}}, + {"device_id": {"device_uuid": {"uuid": "MG-ON1"}}, "endpoint_uuid": {"uuid": "OTP3"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "IP2_CTP1-MGON3_OTP1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "IP2"}}, "endpoint_uuid": {"uuid": "CTP1"}}, + {"device_id": {"device_uuid": {"uuid": "MG-ON3"}}, "endpoint_uuid": {"uuid": "OTP1"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "IP2_CTP2-MGON3_OTP2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "IP2"}}, "endpoint_uuid": {"uuid": "CTP2"}}, + {"device_id": {"device_uuid": {"uuid": "MG-ON3"}}, "endpoint_uuid": {"uuid": "OTP2"}} + ]}, + {"link_id": {"link_uuid": {"uuid": "IP2_CTP3-MGON3_OTP3"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "IP2"}}, "endpoint_uuid": {"uuid": "CTP3"}}, + {"device_id": {"device_uuid": {"uuid": "MG-ON3"}}, "endpoint_uuid": {"uuid": "OTP3"}} + ]} + ] +} diff --git a/src/tests/ecoc24/dump_logs.sh b/src/tests/ecoc24/dump_logs.sh new file mode 100755 index 0000000000000000000000000000000000000000..bb9a35d126a94812d4157d2ba5b4725acbde4caf --- /dev/null +++ b/src/tests/ecoc24/dump_logs.sh @@ -0,0 +1,39 @@ +#!/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. + + +rm -rf tmp/exec + +echo "Collecting logs for E2E..." +mkdir -p tmp/exec/e2e +kubectl --namespace tfs-e2e logs deployments/contextservice server > tmp/exec/e2e/context.log +kubectl --namespace tfs-e2e logs deployments/deviceservice server > tmp/exec/e2e/device.log +kubectl --namespace tfs-e2e logs deployments/serviceservice server > tmp/exec/e2e/service.log +kubectl --namespace tfs-e2e logs deployments/pathcompservice frontend > tmp/exec/e2e/pathcomp-frontend.log +kubectl --namespace tfs-e2e logs deployments/pathcompservice backend > tmp/exec/e2e/pathcomp-backend.log +kubectl --namespace tfs-e2e logs deployments/sliceservice server > tmp/exec/e2e/slice.log +printf "\n" + +echo "Collecting logs for IP..." +mkdir -p tmp/exec/ip +kubectl --namespace tfs-ip logs deployments/contextservice server > tmp/exec/ip/context.log +kubectl --namespace tfs-ip logs deployments/deviceservice server > tmp/exec/ip/device.log +kubectl --namespace tfs-ip logs deployments/serviceservice server > tmp/exec/ip/service.log +kubectl --namespace tfs-ip logs deployments/pathcompservice frontend > tmp/exec/ip/pathcomp-frontend.log +kubectl --namespace tfs-ip logs deployments/pathcompservice backend > tmp/exec/ip/pathcomp-backend.log +kubectl --namespace tfs-ip logs deployments/sliceservice server > tmp/exec/ip/slice.log +printf "\n" + +echo "Done!" diff --git a/src/tests/ecoc24/nginx-ingress-controller-e2e.yaml b/src/tests/ecoc24/nginx-ingress-controller-e2e.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b5f2447aab2ad557e1dc56cdb86a61962494f67d --- /dev/null +++ b/src/tests/ecoc24/nginx-ingress-controller-e2e.yaml @@ -0,0 +1,138 @@ +# 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. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-load-balancer-microk8s-conf-e2e + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-udp-microk8s-conf-e2e + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-tcp-microk8s-conf-e2e + namespace: ingress +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: tfs-ingress-class-e2e + annotations: + ingressclass.kubernetes.io/is-default-class: "false" +spec: + controller: tfs.etsi.org/controller-class-e2e +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nginx-ingress-microk8s-controller-e2e + namespace: ingress + labels: + microk8s-application: nginx-ingress-microk8s-e2e +spec: + selector: + matchLabels: + name: nginx-ingress-microk8s-e2e + updateStrategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + name: nginx-ingress-microk8s-e2e + spec: + terminationGracePeriodSeconds: 60 + restartPolicy: Always + serviceAccountName: nginx-ingress-microk8s-serviceaccount + containers: + - image: k8s.gcr.io/ingress-nginx/controller:v1.2.0 + imagePullPolicy: IfNotPresent + name: nginx-ingress-microk8s + livenessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 5 + lifecycle: + preStop: + exec: + command: + - /wait-shutdown + securityContext: + capabilities: + add: + - NET_BIND_SERVICE + drop: + - ALL + runAsUser: 101 # www-data + env: + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + ports: + - name: http + containerPort: 80 + hostPort: 8001 + protocol: TCP + - name: https + containerPort: 443 + hostPort: 4431 + protocol: TCP + - name: health + containerPort: 10254 + hostPort: 12541 + protocol: TCP + - name: ws + containerPort: 8761 + hostPort: 8761 + protocol: TCP + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-e2e + - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-e2e + - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-e2e + - --election-id=ingress-controller-leader-e2e + - --controller-class=tfs.etsi.org/controller-class-e2e + - --ingress-class=tfs-ingress-class-e2e + - ' ' + - --publish-status-address=127.0.0.1 diff --git a/src/tests/ecoc24/nginx-ingress-controller-ip.yaml b/src/tests/ecoc24/nginx-ingress-controller-ip.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4735ae264a27e6f691d2900701afdd2b0ef2e3d4 --- /dev/null +++ b/src/tests/ecoc24/nginx-ingress-controller-ip.yaml @@ -0,0 +1,138 @@ +# 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. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-load-balancer-microk8s-conf-ip + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-udp-microk8s-conf-ip + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-tcp-microk8s-conf-ip + namespace: ingress +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: tfs-ingress-class-ip + annotations: + ingressclass.kubernetes.io/is-default-class: "false" +spec: + controller: tfs.etsi.org/controller-class-ip +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nginx-ingress-microk8s-controller-ip + namespace: ingress + labels: + microk8s-application: nginx-ingress-microk8s-ip +spec: + selector: + matchLabels: + name: nginx-ingress-microk8s-ip + updateStrategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + name: nginx-ingress-microk8s-ip + spec: + terminationGracePeriodSeconds: 60 + restartPolicy: Always + serviceAccountName: nginx-ingress-microk8s-serviceaccount + containers: + - image: k8s.gcr.io/ingress-nginx/controller:v1.2.0 + imagePullPolicy: IfNotPresent + name: nginx-ingress-microk8s + livenessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 5 + lifecycle: + preStop: + exec: + command: + - /wait-shutdown + securityContext: + capabilities: + add: + - NET_BIND_SERVICE + drop: + - ALL + runAsUser: 101 # www-data + env: + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + ports: + - name: http + containerPort: 80 + hostPort: 8002 + protocol: TCP + - name: https + containerPort: 443 + hostPort: 4432 + protocol: TCP + - name: health + containerPort: 10254 + hostPort: 12542 + protocol: TCP + - name: ws + containerPort: 8762 + hostPort: 8762 + protocol: TCP + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-ip + - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-ip + - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-ip + - --election-id=ingress-controller-leader-ip + - --controller-class=tfs.etsi.org/controller-class-ip + - --ingress-class=tfs-ingress-class-ip + - ' ' + - --publish-status-address=127.0.0.1 diff --git a/src/tests/ecoc24/nginx-ingress-controller-opt.yaml b/src/tests/ecoc24/nginx-ingress-controller-opt.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d62898b60bed4f16999ff71f3c6173fd5d334e12 --- /dev/null +++ b/src/tests/ecoc24/nginx-ingress-controller-opt.yaml @@ -0,0 +1,138 @@ +# 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. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-load-balancer-microk8s-conf-opt + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-udp-microk8s-conf-opt + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-tcp-microk8s-conf-opt + namespace: ingress +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: tfs-ingress-class-opt + annotations: + ingressclass.kubernetes.io/is-default-class: "false" +spec: + controller: tfs.etsi.org/controller-class-opt +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nginx-ingress-microk8s-controller-opt + namespace: ingress + labels: + microk8s-application: nginx-ingress-microk8s-opt +spec: + selector: + matchLabels: + name: nginx-ingress-microk8s-opt + updateStrategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + name: nginx-ingress-microk8s-opt + spec: + terminationGracePeriodSeconds: 60 + restartPolicy: Always + serviceAccountName: nginx-ingress-microk8s-serviceaccount + containers: + - image: k8s.gcr.io/ingress-nginx/controller:v1.2.0 + imagePullPolicy: IfNotPresent + name: nginx-ingress-microk8s + livenessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 5 + lifecycle: + preStop: + exec: + command: + - /wait-shutdown + securityContext: + capabilities: + add: + - NET_BIND_SERVICE + drop: + - ALL + runAsUser: 101 # www-data + env: + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + ports: + - name: http + containerPort: 80 + hostPort: 8003 + protocol: TCP + - name: https + containerPort: 443 + hostPort: 4433 + protocol: TCP + - name: health + containerPort: 10254 + hostPort: 12543 + protocol: TCP + - name: ws + containerPort: 8763 + hostPort: 8763 + protocol: TCP + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-opt + - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-opt + - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-opt + - --election-id=ingress-controller-leader-opt + - --controller-class=tfs.etsi.org/controller-class-opt + - --ingress-class=tfs-ingress-class-opt + - ' ' + - --publish-status-address=127.0.0.1 diff --git a/src/tests/ecoc24/show_deploy.sh b/src/tests/ecoc24/show_deploy.sh new file mode 100755 index 0000000000000000000000000000000000000000..295dd29b4d132c6f809a01e5dc2cb17a956531a2 --- /dev/null +++ b/src/tests/ecoc24/show_deploy.sh @@ -0,0 +1,42 @@ +#!/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. + +######################################################################################################################## +# Automated steps start here +######################################################################################################################## + +echo "E2E Deployment Resources:" +kubectl --namespace tfs-e2e get all +printf "\n" + +echo "E2E Deployment Ingress:" +kubectl --namespace tfs-e2e get ingress +printf "\n" + +echo "IP Deployment Resources:" +kubectl --namespace tfs-ip get all +printf "\n" + +echo "IP Deployment Ingress:" +kubectl --namespace tfs-ip get ingress +printf "\n" + +echo "Optical Deployment Resources:" +kubectl --namespace tfs-opt get all +printf "\n" + +echo "Optical Deployment Ingress:" +kubectl --namespace tfs-opt get ingress +printf "\n" diff --git a/src/tests/ecoc24/subscription_ws_e2e.sh b/src/tests/ecoc24/subscription_ws_e2e.sh new file mode 100755 index 0000000000000000000000000000000000000000..33f5a5109044b1aa7384da41965d1b84a29cb945 --- /dev/null +++ b/src/tests/ecoc24/subscription_ws_e2e.sh @@ -0,0 +1,42 @@ +#!/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. + + +######################################################################################################################## +# Read deployment settings +######################################################################################################################## + +# If not already set, set the namespace where CockroackDB will be deployed. +export SUBSCRIPTION_WS_NAMESPACE=${SUBSCRIPTION_WS_NAMESPACE:-"tfs-e2e"} + +# If not already set, set the external port interface will be exposed to. +export SUBSCRIPTION_WS_EXT_PORT=${SUBSCRIPTION_WS_EXT_PORT:-"8761"} + + +######################################################################################################################## +# Automated steps start here +######################################################################################################################## + + +echo "Subscription WebSocket Port Mapping" +echo ">>> ExposeSubscription WebSocket port (${SUBSCRIPTION_WS_EXT_PORT}->${SUBSCRIPTION_WS_EXT_PORT})" +PATCH='{"data": {"'${SUBSCRIPTION_WS_EXT_PORT}'": "'${SUBSCRIPTION_WS_NAMESPACE}'/nbiservice:'${SUBSCRIPTION_WS_EXT_PORT}'"}}' +kubectl patch configmap nginx-ingress-tcp-microk8s-conf-e2e --namespace ingress --patch "${PATCH}" + +PORT_MAP='{"containerPort": '${SUBSCRIPTION_WS_EXT_PORT}', "hostPort": '${SUBSCRIPTION_WS_EXT_PORT}'}' +CONTAINER='{"name": "nginx-ingress-microk8s", "ports": ['${PORT_MAP}']}' +PATCH='{"spec": {"template": {"spec": {"containers": ['${CONTAINER}']}}}}' +kubectl patch daemonset nginx-ingress-microk8s-controller-e2e --namespace ingress --patch "${PATCH}" +echo diff --git a/src/tests/ecoc24/subscription_ws_ip.sh b/src/tests/ecoc24/subscription_ws_ip.sh new file mode 100755 index 0000000000000000000000000000000000000000..795469e8fcb026cd1734a6330fc7b8c4a1e97aac --- /dev/null +++ b/src/tests/ecoc24/subscription_ws_ip.sh @@ -0,0 +1,42 @@ +#!/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. + + +######################################################################################################################## +# Read deployment settings +######################################################################################################################## + +# If not already set, set the namespace where CockroackDB will be deployed. +export SUBSCRIPTION_WS_NAMESPACE=${SUBSCRIPTION_WS_NAMESPACE:-"tfs-ip"} + +# If not already set, set the external port interface will be exposed to. +export SUBSCRIPTION_WS_INT_PORT=${SUBSCRIPTION_WS_INT_PORT:-"8762"} +######################################################################################################################## +# Automated steps start here +######################################################################################################################## + + + + +echo "Subscription WebSocket Port Mapping" +echo ">>> ExposeSubscription WebSocket port (${SUBSCRIPTION_WS_INT_PORT}->${SUBSCRIPTION_WS_INT_PORT})" +PATCH='{"data": {"'${SUBSCRIPTION_WS_INT_PORT}'": "'${SUBSCRIPTION_WS_NAMESPACE}'/nbiservice:'${SUBSCRIPTION_WS_INT_PORT}'"}}' +kubectl patch configmap nginx-ingress-tcp-microk8s-conf-ip --namespace ingress --patch "${PATCH}" + +PORT_MAP='{"containerPort": '${SUBSCRIPTION_WS_INT_PORT}', "hostPort": '${SUBSCRIPTION_WS_INT_PORT}'}' +CONTAINER='{"name": "nginx-ingress-microk8s", "ports": ['${PORT_MAP}']}' +PATCH='{"spec": {"template": {"spec": {"containers": ['${CONTAINER}']}}}}' +kubectl patch daemonset nginx-ingress-microk8s-controller-ip --namespace ingress --patch "${PATCH}" +echo diff --git a/src/tests/ecoc24/tfs-ingress-e2e.yaml b/src/tests/ecoc24/tfs-ingress-e2e.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1b82ae44ca0945163701bc18a250a1742e921519 --- /dev/null +++ b/src/tests/ecoc24/tfs-ingress-e2e.yaml @@ -0,0 +1,67 @@ +# 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. + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: tfs-ingress-e2e + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: tfs-ingress-class-e2e + rules: + - http: + paths: + - path: /webui(/|$)(.*) + pathType: Prefix + backend: + service: + name: webuiservice + port: + number: 8004 + - path: /grafana(/|$)(.*) + pathType: Prefix + backend: + service: + name: webuiservice + port: + number: 3000 + - path: /()(restconf/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 + - path: /()(tfs-api/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 + - path: /()(bmw/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 + - path: /()(qkd_app/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 diff --git a/src/tests/ecoc24/tfs-ingress-ip.yaml b/src/tests/ecoc24/tfs-ingress-ip.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6ee6ada32f3f278c5f36dfbb08d936a432bfef23 --- /dev/null +++ b/src/tests/ecoc24/tfs-ingress-ip.yaml @@ -0,0 +1,67 @@ +# 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. + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: tfs-ingress-ip + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: tfs-ingress-class-ip + rules: + - http: + paths: + - path: /webui(/|$)(.*) + pathType: Prefix + backend: + service: + name: webuiservice + port: + number: 8004 + - path: /grafana(/|$)(.*) + pathType: Prefix + backend: + service: + name: webuiservice + port: + number: 3000 + - path: /()(restconf/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 + - path: /()(tfs-api/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 + - path: /()(bmw/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 + - path: /()(qkd_app/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 diff --git a/src/tests/ecoc24/tfs-ingress-opt.yaml b/src/tests/ecoc24/tfs-ingress-opt.yaml new file mode 100644 index 0000000000000000000000000000000000000000..50ddd4f046acf74cfe57e73f55e29e1b77fd32af --- /dev/null +++ b/src/tests/ecoc24/tfs-ingress-opt.yaml @@ -0,0 +1,67 @@ +# 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. + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: tfs-ingress-opt + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: tfs-ingress-class-opt + rules: + - http: + paths: + - path: /webui(/|$)(.*) + pathType: Prefix + backend: + service: + name: webuiservice + port: + number: 8004 + - path: /grafana(/|$)(.*) + pathType: Prefix + backend: + service: + name: webuiservice + port: + number: 3000 + - path: /()(restconf/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 + - path: /()(tfs-api/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 + - path: /()(bmw/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 + - path: /()(qkd_app/.*) + pathType: Prefix + backend: + service: + name: nbiservice + port: + number: 8080 diff --git a/src/vnt_manager/.gitlab-ci.yml b/src/vnt_manager/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..b338f97ac8a517abbfbdc5112bd54d5d6545afd1 --- /dev/null +++ b/src/vnt_manager/.gitlab-ci.yml @@ -0,0 +1,38 @@ +# 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. + +# build, tag and push the Docker image to the gitlab registry +build vntmanager: + variables: + IMAGE_NAME: 'vntmanager' # name of the microservice + IMAGE_TAG: 'latest' # tag of the container image (production, development, etc) + stage: build + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + script: + - docker buildx build -t "$IMAGE_NAME:$IMAGE_TAG" -f ./src/$IMAGE_NAME/Dockerfile . + - docker tag "$IMAGE_NAME:$IMAGE_TAG" "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + - docker push "$CI_REGISTRY_IMAGE/$IMAGE_NAME:$IMAGE_TAG" + after_script: + - docker images --filter="dangling=true" --quiet | xargs -r docker rmi + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH)' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"' + - changes: + - src/$IMAGE_NAME/**/*.{py,in,yml} + - src/$IMAGE_NAME/Dockerfile + - src/$IMAGE_NAME/tests/*.py + - src/$IMAGE_NAME/tests/Dockerfile + - manifests/${IMAGE_NAME}service.yaml + - .gitlab-ci.yml diff --git a/src/vnt_manager/Config.py b/src/vnt_manager/Config.py new file mode 100644 index 0000000000000000000000000000000000000000..bbfc943b68af13a11e562abbc8680ade71db8f02 --- /dev/null +++ b/src/vnt_manager/Config.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/vnt_manager/Dockerfile b/src/vnt_manager/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..2680336e962240c3938b06ffb47079bd6dab1368 --- /dev/null +++ b/src/vnt_manager/Dockerfile @@ -0,0 +1,87 @@ +# 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 python:3.9-slim + +# Install dependencies +RUN apt-get --yes --quiet --quiet update && \ + apt-get --yes --quiet --quiet install wget g++ && \ + rm -rf /var/lib/apt/lists/* + +# Set Python to show logs as they occur +ENV PYTHONUNBUFFERED=0 +ENV PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python + +# Download the gRPC health probe +RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ + wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ + chmod +x /bin/grpc_health_probe + +# Creating a user for security reasons +RUN groupadd -r teraflow && useradd -u 1001 --no-log-init -r -m -g teraflow teraflow +USER teraflow + +# set working directory +RUN mkdir -p /home/teraflow/controller/common/ +WORKDIR /home/teraflow/controller + +# Get Python packages per module +ENV VIRTUAL_ENV=/home/teraflow/venv +RUN python3 -m venv ${VIRTUAL_ENV} +ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" + +# Get generic Python packages +RUN python3 -m pip install --upgrade pip +RUN python3 -m pip install --upgrade setuptools wheel +RUN python3 -m pip install --upgrade pip-tools + +# Get common Python packages +# Note: this step enables sharing the previous Docker build steps among all the Python components +COPY --chown=teraflow:teraflow common_requirements.in common_requirements.in +RUN pip-compile --quiet --output-file=common_requirements.txt common_requirements.in +RUN python3 -m pip install -r common_requirements.txt + +# Add common files into working directory +WORKDIR /home/teraflow/controller/common +COPY --chown=teraflow:teraflow src/common/. ./ +RUN rm -rf proto + +# Create proto sub-folder, copy .proto files, and generate Python code +RUN mkdir -p /home/teraflow/controller/common/proto +WORKDIR /home/teraflow/controller/common/proto +RUN touch __init__.py +COPY --chown=teraflow:teraflow proto/*.proto ./ +RUN python3 -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. *.proto +RUN rm *.proto +RUN find . -type f -exec sed -i -E 's/(import\ .*)_pb2/from . \1_pb2/g' {} \; + +# Create module sub-folders +RUN mkdir -p /home/teraflow/controller/vnt_manager +WORKDIR /home/teraflow/controller + +# Get Python packages per module +COPY --chown=teraflow:teraflow ./src/vnt_manager/requirements.in vnt_manager/requirements.in +# consider common and specific requirements to avoid inconsistencies with dependencies +RUN pip-compile --quiet --output-file=vnt_manager/requirements.txt vnt_manager/requirements.in common_requirements.in +RUN python3 -m pip install -r vnt_manager/requirements.txt + +# Add component files into working directory +COPY src/context/__init__.py context/__init__.py +COPY src/context/client/. context/client/ +COPY src/device/__init__.py device/__init__.py +COPY src/device/client/. device/client/ +COPY src/vnt_manager/. vnt_manager/ + +# Start the service +ENTRYPOINT ["python", "-m", "vnt_manager.service"] diff --git a/src/vnt_manager/__init__.py b/src/vnt_manager/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bbfc943b68af13a11e562abbc8680ade71db8f02 --- /dev/null +++ b/src/vnt_manager/__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/vnt_manager/client/VNTManagerClient.py b/src/vnt_manager/client/VNTManagerClient.py new file mode 100644 index 0000000000000000000000000000000000000000..8ecb3dc0d8f7c937c66b5af6e2f3ae715575b425 --- /dev/null +++ b/src/vnt_manager/client/VNTManagerClient.py @@ -0,0 +1,104 @@ +# 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 + +import grpc + +from common.Constants import ServiceNameEnum +from common.proto.context_pb2 import Empty +from common.proto.vnt_manager_pb2 import VNTSubscriptionRequest, VNTSubscriptionReply +from common.proto.vnt_manager_pb2_grpc import VNTManagerServiceStub +from common.Settings import get_service_host, get_service_port_grpc +from common.tools.client.RetryDecorator import delay_exponential, retry +from common.tools.grpc.Tools import grpc_message_to_json +from common.proto.context_pb2 import ( + Link, LinkId, LinkIdList, LinkList, +) +from common.tools.grpc.Tools import grpc_message_to_json_string + +LOGGER = logging.getLogger(__name__) +MAX_RETRIES = 15 +DELAY_FUNCTION = delay_exponential(initial=0.01, increment=2.0, maximum=5.0) +RETRY_DECORATOR = retry( + max_retries=MAX_RETRIES, + delay_function=DELAY_FUNCTION, + prepare_method_name="connect", +) + + +class VNTManagerClient: + def __init__(self, host=None, port=None): + if not host: + host = get_service_host(ServiceNameEnum.VNTMANAGER) + if not port: + port = get_service_port_grpc(ServiceNameEnum.VNTMANAGER) + self.endpoint = "{:s}:{:s}".format(str(host), str(port)) + LOGGER.debug("Creating channel to {:s}...".format(str(self.endpoint))) + self.channel = None + self.stub = None + self.connect() + LOGGER.debug("Channel created") + + def connect(self): + self.channel = grpc.insecure_channel(self.endpoint) + self.stub = VNTManagerServiceStub(self.channel) + + def close(self): + if self.channel is not None: + self.channel.close() + self.channel = None + self.stub = None + + @RETRY_DECORATOR + def VNTSubscript(self, request: VNTSubscriptionRequest) -> VNTSubscriptionReply: + LOGGER.debug("Subscript request: {:s}".format(str(grpc_message_to_json(request)))) + response = self.stub.VNTSubscript(request) + LOGGER.debug("Subscript result: {:s}".format(str(grpc_message_to_json(response)))) + return response + + @RETRY_DECORATOR + def ListVirtualLinkIds(self, request: Empty) -> LinkIdList: + LOGGER.debug('ListVirtualLinkIds request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.ListVirtualLinkIds(request) + LOGGER.debug('ListVirtualLinkIds result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def ListVirtualLinks(self, request: Empty) -> LinkList: + LOGGER.debug('ListVirtualLinks request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.ListVirtualLinks(request) + LOGGER.debug('ListVirtualLinks result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def GetVirtualLink(self, request: LinkId) -> Link: + LOGGER.debug('GetVirtualLink request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.GetVirtualLink(request) + LOGGER.debug('GetVirtualLink result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def SetVirtualLink(self, request: Link) -> LinkId: + LOGGER.debug('SetVirtualLink request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.SetVirtualLink(request) + LOGGER.debug('SetVirtualLink result: {:s}'.format(grpc_message_to_json_string(response))) + return response + + @RETRY_DECORATOR + def RemoveVirtualLink(self, request: LinkId) -> Empty: + LOGGER.debug('RemoveVirtualLink request: {:s}'.format(grpc_message_to_json_string(request))) + response = self.stub.RemoveVirtualLink(request) + LOGGER.debug('RemoveVirtualLink result: {:s}'.format(grpc_message_to_json_string(response))) + return response diff --git a/src/vnt_manager/client/__init__.py b/src/vnt_manager/client/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bbfc943b68af13a11e562abbc8680ade71db8f02 --- /dev/null +++ b/src/vnt_manager/client/__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/vnt_manager/requirements.in b/src/vnt_manager/requirements.in new file mode 100644 index 0000000000000000000000000000000000000000..6f9f590845cf3c925b2da23f127bc4aa942253b9 --- /dev/null +++ b/src/vnt_manager/requirements.in @@ -0,0 +1,15 @@ +# 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. + +websockets==12.0 diff --git a/src/vnt_manager/service/VNTManagerService.py b/src/vnt_manager/service/VNTManagerService.py new file mode 100644 index 0000000000000000000000000000000000000000..80d635ffe475c7840cf9bd0c0650471bae17cb3e --- /dev/null +++ b/src/vnt_manager/service/VNTManagerService.py @@ -0,0 +1,35 @@ +# 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 common.Constants import ServiceNameEnum +from common.proto.vnt_manager_pb2_grpc import add_VNTManagerServiceServicer_to_server +from common.Settings import get_service_port_grpc +from common.tools.service.GenericGrpcService import GenericGrpcService +from .VNTManagerServiceServicerImpl import VNTManagerServiceServicerImpl + +LOGGER = logging.getLogger(__name__) + + +class VNTManagerService(GenericGrpcService): + def __init__(self, cls_name: str = __name__): + port = get_service_port_grpc(ServiceNameEnum.VNTMANAGER) + super().__init__(port, cls_name=cls_name) + self.vntmanager_servicer = VNTManagerServiceServicerImpl() + + def install_servicers(self): + add_VNTManagerServiceServicer_to_server( + self.vntmanager_servicer, self.server + ) diff --git a/src/vnt_manager/service/VNTManagerServiceServicerImpl.py b/src/vnt_manager/service/VNTManagerServiceServicerImpl.py new file mode 100644 index 0000000000000000000000000000000000000000..d684e044efd972bd7705f6c1a448b0a5be23431b --- /dev/null +++ b/src/vnt_manager/service/VNTManagerServiceServicerImpl.py @@ -0,0 +1,183 @@ +# 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 grpc +import json +import logging +import threading +import time +from websockets.sync.client import connect +from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME +from common.method_wrappers.Decorator import MetricsPool, safe_and_metered_rpc_method +from common.proto.context_pb2 import ContextId, Empty, Link, LinkId, LinkList, TopologyId +from common.proto.vnt_manager_pb2 import VNTSubscriptionRequest, VNTSubscriptionReply +from common.proto.vnt_manager_pb2_grpc import VNTManagerServiceServicer +from common.tools.grpc.Tools import grpc_message_to_json, grpc_message_to_json_string +from common.tools.object_factory.Context import json_context_id +from common.tools.object_factory.Topology import json_topology_id +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from .vntm_config_device import configure, deconfigure + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool("VNTManager", "RPC") + +context_client: ContextClient = ContextClient() + +JSON_ADMIN_CONTEXT_ID = json_context_id(DEFAULT_CONTEXT_NAME) +ADMIN_CONTEXT_ID = ContextId(**JSON_ADMIN_CONTEXT_ID) +ADMIN_TOPOLOGY_ID = TopologyId(**json_topology_id(DEFAULT_TOPOLOGY_NAME, context_id=JSON_ADMIN_CONTEXT_ID)) + +GET_EVENT_TIMEOUT = 0.5 + + +class VNTMEventDispatcher(threading.Thread): + def __init__(self, host, port) -> None: + LOGGER.debug('Creating VTNM connector...') + self.host = host + self.port = port + super().__init__(name='VNTMEventDispatcher', daemon=True) + self._terminate = threading.Event() + LOGGER.debug('VNTM connector created') + + def start(self) -> None: + self._terminate.clear() + return super().start() + + def stop(self): + self._terminate.set() + + + def send_msg(self, msg): + try: + self.websocket.send(msg) + except Exception as e: + LOGGER.info(e) + + def recv_msg(self): + message = self.websocket.recv() + return message + + def run(self) -> None: + + time.sleep(5) + events_collector = EventsCollector( + context_client, log_events_received=True, + activate_context_collector = False, + activate_topology_collector = True, + activate_device_collector = False, + activate_link_collector = False, + activate_service_collector = False, + activate_slice_collector = False, + activate_connection_collector = False,) + events_collector.start() + + + url = "ws://" + str(self.host) + ":" + str(self.port) + LOGGER.debug('Connecting to {}'.format(url)) + + try: + LOGGER.info("Connecting to events server...: {}".format(url)) + self.websocket = connect(url) + except Exception as ex: + LOGGER.error('Error connecting to {}'.format(url)) + else: + LOGGER.info('Connected to {}'.format(url)) + context_id = json_context_id(DEFAULT_CONTEXT_NAME) + topology_id = json_topology_id(DEFAULT_TOPOLOGY_NAME, context_id) + + try: + topology_details = context_client.GetTopologyDetails(TopologyId(**topology_id)) + except Exception as ex: + LOGGER.warning('No topology found') + else: + self.send_msg(grpc_message_to_json_string(topology_details)) + + while not self._terminate.is_set(): + event = events_collector.get_event(block=True, timeout=GET_EVENT_TIMEOUT) + LOGGER.info('Event type: {}'.format(event)) + if event is None: continue + LOGGER.debug('Received event: {}'.format(event)) + topology_details = context_client.GetTopologyDetails(TopologyId(**topology_id)) + + to_send = grpc_message_to_json_string(topology_details) + + self.send_msg(to_send) + + LOGGER.info('Exiting') + events_collector.stop() + + +class VNTManagerServiceServicerImpl(VNTManagerServiceServicer): + def __init__(self): + LOGGER.debug("Creating Servicer...") + LOGGER.debug("Servicer Created") + self.links = [] + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def VNTSubscript(self, request: VNTSubscriptionRequest, context: grpc.ServicerContext) -> VNTSubscriptionReply: + LOGGER.info("Subscript request: {:s}".format(str(grpc_message_to_json(request)))) + reply = VNTSubscriptionReply() + reply.subscription = "OK" + + self.event_dispatcher = VNTMEventDispatcher(request.host, int(request.port)) + self.host = request.host + self.port = request.port + self.event_dispatcher.start() + return reply + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def ListVirtualLinks(self, request : Empty, context : grpc.ServicerContext) -> LinkList: + return [link for link in context_client.ListLinks(Empty()).links if link.virtual] + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def GetVirtualLink(self, request : LinkId, context : grpc.ServicerContext) -> Link: + link = context_client.GetLink(request) + return link if link.virtual else Empty() + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def SetVirtualLink(self, request : Link, context : grpc.ServicerContext) -> LinkId: + try: + LOGGER.info('SETTING virtual link') + self.event_dispatcher.send_msg(grpc_message_to_json_string(request)) + # configure('CSGW1', 'xe5', 'CSGW2', 'xe5', 'ecoc2024-1') + response = self.event_dispatcher.recv_msg() + message_json = json.loads(response) + link = Link(**message_json) + context_client.SetLink(link) + except Exception as e: + LOGGER.error('Exception setting virtual link={}\n\t{}'.format(request.link_id.link_uuid.uuid, e)) + return request.link_id + + @safe_and_metered_rpc_method(METRICS_POOL, LOGGER) + def RemoveVirtualLink(self, request : LinkId, context : grpc.ServicerContext) -> Empty: + try: + self.event_dispatcher.send_msg(grpc_message_to_json_string(request)) + # deconfigure('CSGW1', 'xe5', 'CSGW2', 'xe5', 'ecoc2024-1') + response = self.event_dispatcher.recv_msg() + message_json = json.loads(response) + link_id = LinkId(**message_json) + context_client.RemoveLink(link_id) + + LOGGER.info('Removed') + except Exception as e: + msg_error = 'Exception removing virtual link={}\n\t{}'.format(request.link_uuid.uuid, e) + LOGGER.error(msg_error) + return msg_error + else: + context_client.RemoveLink(request) + LOGGER.info('Removed') + + return Empty() diff --git a/src/vnt_manager/service/__init__.py b/src/vnt_manager/service/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bbfc943b68af13a11e562abbc8680ade71db8f02 --- /dev/null +++ b/src/vnt_manager/service/__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/vnt_manager/service/__main__.py b/src/vnt_manager/service/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..a67eb4cfdf657c00373ce93d617425eda2f00981 --- /dev/null +++ b/src/vnt_manager/service/__main__.py @@ -0,0 +1,66 @@ +# 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, signal, sys, threading +from prometheus_client import start_http_server +from common.Constants import ServiceNameEnum +from common.Settings import ( + ENVVAR_SUFIX_SERVICE_HOST, ENVVAR_SUFIX_SERVICE_PORT_GRPC, + get_env_var_name, get_log_level, get_metrics_port, + wait_for_environment_variables +) +from .VNTManagerService import VNTManagerService + +LOG_LEVEL = get_log_level() +logging.basicConfig(level=LOG_LEVEL, format="[%(asctime)s] %(levelname)s:%(name)s:%(message)s") +LOGGER = logging.getLogger(__name__) + +terminate = threading.Event() + +def signal_handler(signal, frame): # pylint: disable=redefined-outer-name,unused-argument + LOGGER.warning("Terminate signal received") + terminate.set() + +def main(): + LOGGER.info("Starting...") + + wait_for_environment_variables([ + get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + get_env_var_name(ServiceNameEnum.DEVICE, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.DEVICE, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + ]) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + # Start metrics server + metrics_port = get_metrics_port() + start_http_server(metrics_port) + + # Starting VNTManager service + grpc_service = VNTManagerService() + grpc_service.start() + + # Wait for Ctrl+C or termination signal + while not terminate.wait(timeout=1): pass + + LOGGER.info("Terminating...") + grpc_service.stop() + + LOGGER.info("Bye") + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/vnt_manager/service/vntm_config_device.py b/src/vnt_manager/service/vntm_config_device.py new file mode 100644 index 0000000000000000000000000000000000000000..4735ed31f185ba221033a7611b2b1af3f90c1688 --- /dev/null +++ b/src/vnt_manager/service/vntm_config_device.py @@ -0,0 +1,184 @@ +# 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 typing import Dict +from common.proto.context_pb2 import ConfigRule +from common.tools.context_queries.Device import get_device +from common.tools.object_factory.ConfigRule import json_config_rule_set, json_config_rule_delete +from context.client.ContextClient import ContextClient +from device.client.DeviceClient import DeviceClient + +##### Config Rule Composers #################################################### + +def compose_config_rule(resource_key, resource_value, delete) -> Dict: + json_config_rule = json_config_rule_delete if delete else json_config_rule_set + return ConfigRule(**json_config_rule(resource_key, resource_value)) + +def network_instance(ni_name, ni_type, ni_router_id=None, ni_route_distinguisher=None, delete=False) -> Dict: + path = '/network_instance[{:s}]'.format(ni_name) + data = {'name': ni_name, 'type': ni_type} + if ni_router_id is not None: data['router_id'] = ni_router_id + if ni_route_distinguisher is not None: data['route_distinguisher'] = ni_route_distinguisher + return compose_config_rule(path, data, delete) + +def network_instance_add_protocol_bgp(ni_name, ni_type, ni_router_id, ni_bgp_as, neighbors=[], delete=False)-> Dict: + path = '/network_instance[{:s}]/protocols[BGP]'.format(ni_name) + data = { + 'name': ni_name, 'type': ni_type, 'router_id': ni_router_id, 'identifier': 'BGP', + 'protocol_name': ni_bgp_as, 'as': ni_bgp_as + } + if len(neighbors) > 0: + data['neighbors'] = [ + {'ip_address': neighbor_ip_address, 'remote_as': neighbor_remote_as} + for neighbor_ip_address, neighbor_remote_as in neighbors + ] + return compose_config_rule(path, data, delete) + +def network_instance_add_protocol_direct(ni_name, ni_type, delete=False) -> Dict: + path = '/network_instance[{:s}]/protocols[DIRECTLY_CONNECTED]'.format(ni_name) + data = { + 'name': ni_name, 'type': ni_type, 'identifier': 'DIRECTLY_CONNECTED', + 'protocol_name': 'DIRECTLY_CONNECTED' + } + return compose_config_rule(path, data, delete) + +def network_instance_add_protocol_static(ni_name, ni_type, delete=False) -> Dict: + path = '/network_instance[{:s}]/protocols[STATIC]'.format(ni_name) + data = { + 'name': ni_name, 'type': ni_type, 'identifier': 'STATIC', + 'protocol_name': 'STATIC' + } + return compose_config_rule(path, data, delete) + +def network_instance_add_table_connection( + ni_name, src_protocol, dst_protocol, address_family, default_import_policy, bgp_as=None, delete=False +) -> Dict: + path = '/network_instance[{:s}]/table_connections[{:s}][{:s}][{:s}]'.format( + ni_name, src_protocol, dst_protocol, address_family + ) + data = { + 'name': ni_name, 'src_protocol': src_protocol, 'dst_protocol': dst_protocol, + 'address_family': address_family, 'default_import_policy': default_import_policy, + } + if bgp_as is not None: data['as'] = bgp_as + return compose_config_rule(path, data, delete) + +def interface( + name, index, description=None, if_type=None, vlan_id=None, mtu=None, ipv4_address_prefix=None, + enabled=None, delete=False +) -> Dict: + path = '/interface[{:s}]/subinterface[{:d}]'.format(name, index) + data = {'name': name, 'index': index} + if description is not None: data['description'] = description + if if_type is not None: data['type' ] = if_type + if vlan_id is not None: data['vlan_id' ] = vlan_id + if mtu is not None: data['mtu' ] = mtu + if enabled is not None: data['enabled' ] = enabled + if ipv4_address_prefix is not None: + ipv4_address, ipv4_prefix = ipv4_address_prefix + data['address_ip' ] = ipv4_address + data['address_prefix'] = ipv4_prefix + return compose_config_rule(path, data, delete) + +def network_instance_interface(ni_name, ni_type, if_name, if_index, delete=False) -> Dict: + path = '/network_instance[{:s}]/interface[{:s}.{:d}]'.format(ni_name, if_name, if_index) + data = {'name': ni_name, 'type': ni_type, 'id': if_name, 'interface': if_name, 'subinterface': if_index} + return compose_config_rule(path, data, delete) + +# configure('CSGW1', 'xe5', 'CSGW2', 'xe5', 'ecoc2024-1') +# deconfigure('CSGW1', 'xe5', 'CSGW2', 'xe5', 'ecoc2024-1') + +def configure(router_a, port_a, router_b, port_b, ni_name): + context_client = ContextClient() + device_client = DeviceClient() + + client_if_name = 'ce1' + client_if_addr = {'CSGW1': ('192.168.10.1', 24), 'CSGW2': ('192.168.20.1', 24)} + bgp_router_addresses = {'CSGW1': '192.168.150.1', 'CSGW2': '192.168.150.2'} + + locations = [ + {'router': router_a, 'port': port_a, 'neighbor': router_b}, + {'router': router_b, 'port': port_b, 'neighbor': router_a}, + ] + for location in locations: + router = location['router'] + port = location['port'] + neighbor = location['neighbor'] + + client_ipv4_address_prefix = client_if_addr[router] + bgp_router_address = bgp_router_addresses[router] + bgp_neighbor_address = bgp_router_addresses[neighbor] + + config_rules = [ + network_instance(ni_name, 'L3VRF', bgp_router_address, '65001:1'), + network_instance_add_protocol_direct(ni_name, 'L3VRF'), + network_instance_add_protocol_static(ni_name, 'L3VRF'), + network_instance_add_protocol_bgp(ni_name, 'L3VRF', bgp_router_address, '65001', neighbors=[ + (bgp_neighbor_address, '65001') + ]), + network_instance_add_table_connection( + ni_name, 'DIRECTLY_CONNECTED', 'BGP', 'IPV4', 'ACCEPT_ROUTE', bgp_as='65001' + ), + network_instance_add_table_connection( + ni_name, 'STATIC', 'BGP', 'IPV4', 'ACCEPT_ROUTE', bgp_as='65001' + ), + + interface(client_if_name, 0, if_type='ethernetCsmacd', mtu=1500), + network_instance_interface(ni_name, 'L3VRF', client_if_name, 0), + interface(client_if_name, 0, if_type='ethernetCsmacd', mtu=1500, + ipv4_address_prefix=client_ipv4_address_prefix, enabled=True), + + interface(port, 0, if_type='ethernetCsmacd', mtu=1500), + network_instance_interface(ni_name, 'L3VRF', port, 0), + interface(port, 0, if_type='ethernetCsmacd', mtu=1500, + ipv4_address_prefix=(bgp_router_address, 24), enabled=True), + ] + + device = get_device( + context_client, router, rw_copy=True, include_endpoints=False, + include_config_rules=False, include_components=False + ) + device.device_config.config_rules.extend(config_rules) + device_client.ConfigureDevice(device) + + +def deconfigure(router_a, port_a, router_b, port_b, ni_name): + context_client = ContextClient() + device_client = DeviceClient() + + client_if_name = 'ce1' + + locations = [ + {'router': router_a, 'port': port_a, 'neighbor': router_b}, + {'router': router_b, 'port': port_b, 'neighbor': router_a}, + ] + for location in locations: + router = location['router'] + port = location['port'] + #neighbor = location['neighbor'] + + config_rules = [ + network_instance_interface(ni_name, 'L3VRF', client_if_name, 0, delete=True), + network_instance_interface(ni_name, 'L3VRF', port, 0, delete=True), + #interface(client_if_name, 0, delete=True), + #interface(port, 0, delete=True), + network_instance(ni_name, 'L3VRF', delete=True), + ] + + device = get_device( + context_client, router, rw_copy=True, include_endpoints=False, + include_config_rules=False, include_components=False + ) + device.device_config.config_rules.extend(config_rules) + device_client.ConfigureDevice(device)