diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dcde3bb15412b6a4e48faf19bf247f4570d216f4..873b2ac4b22143b37a96fd3ded467f586d61c8ac 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/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/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/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 555173cf5ece4833bea8829ae7be593599b3dde2..8d2e733d462ae743c7187eb9b6a58d7da14033a7 100755 --- a/my_deploy.sh +++ b/my_deploy.sh @@ -65,6 +65,9 @@ export TFS_COMPONENTS="context device pathcomp service slice nbi webui" # 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 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_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/common/Constants.py b/src/common/Constants.py index bd4b9031fb1f33a5ad6c46e537961796bd3e2dae..aa15d66c5937c3f5a589ddf9cbb432d08c4b49b0 100644 --- a/src/common/Constants.py +++ b/src/common/Constants.py @@ -61,6 +61,7 @@ class ServiceNameEnum(Enum): FORECASTER = 'forecaster' E2EORCHESTRATOR = 'e2e-orchestrator' OPTICALCONTROLLER = 'opticalcontroller' + VNTMANAGER = 'vnt-manager' BGPLS = 'bgpls-speaker' QKD_APP = 'qkd_app' KPIMANAGER = 'kpi-manager' @@ -100,6 +101,7 @@ 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, 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 ad111c2834586e0a949ea0396afb80124c7ed2d7..ec1c054858b5f97dcdff36e2ccd4c8039942b51e 100644 --- a/src/nbi/Dockerfile +++ b/src/nbi/Dockerfile @@ -88,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/tests/.gitlab-ci.yml b/src/tests/.gitlab-ci.yml index c64db5d8fef7661e37f271429cd4c1c5ff328b7a..e7d96f298323f4698f5cf9ce89789788677d39f6 100644 --- a/src/tests/.gitlab-ci.yml +++ b/src/tests/.gitlab-ci.yml @@ -20,3 +20,4 @@ include: #- local: '/src/tests/nfvsdn22/.gitlab-ci.yml' #- local: '/src/tests/ofc23/.gitlab-ci.yml' - local: '/src/tests/ofc24/.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)