From 99e0d57ab6d79e23b0aa0a831ff9838391dc8fe1 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Tue, 18 Oct 2022 13:24:31 +0000 Subject: [PATCH 01/55] NFV-SDN'22 added symbolic links --- nfvsdn22 | 1 + src/tests/nfvsdn22 | 1 + 2 files changed, 2 insertions(+) create mode 120000 nfvsdn22 create mode 120000 src/tests/nfvsdn22 diff --git a/nfvsdn22 b/nfvsdn22 new file mode 120000 index 000000000..ac93a84be --- /dev/null +++ b/nfvsdn22 @@ -0,0 +1 @@ +src/tests/nfvsdn22/ \ No newline at end of file diff --git a/src/tests/nfvsdn22 b/src/tests/nfvsdn22 new file mode 120000 index 000000000..e8122da56 --- /dev/null +++ b/src/tests/nfvsdn22 @@ -0,0 +1 @@ +./scenario2 \ No newline at end of file -- GitLab From 1b4cee1098dbfb2e1a420b72fe3ff2efa2cd3ee0 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 26 Oct 2022 12:32:05 +0000 Subject: [PATCH 02/55] NFV-SDN'22: - Created initial scenario deployment scripts and manifests --- manifests/webuiservice.yaml | 80 ++++++------ src/tests/scenario2/.gitignore | 2 + src/tests/scenario2/README.md | 1 - src/tests/scenario2/__init__.py | 14 ++ src/tests/scenario2/deploy_all.sh | 32 +++++ src/tests/scenario2/deploy_specs_dom1.sh | 17 +++ src/tests/scenario2/deploy_specs_dom2.sh | 17 +++ src/tests/scenario2/deploy_specs_dom3.sh | 17 +++ src/tests/scenario2/deploy_specs_dom4.sh | 17 +++ .../nginx-ingress-controller-dom1.yaml | 120 ++++++++++++++++++ .../nginx-ingress-controller-dom2.yaml | 120 ++++++++++++++++++ .../nginx-ingress-controller-dom3.yaml | 120 ++++++++++++++++++ .../nginx-ingress-controller-dom4.yaml | 120 ++++++++++++++++++ src/tests/scenario2/tfs-ingress-dom1.yaml | 39 ++++++ src/tests/scenario2/tfs-ingress-dom2.yaml | 39 ++++++ src/tests/scenario2/tfs-ingress-dom3.yaml | 39 ++++++ src/tests/scenario2/tfs-ingress-dom4.yaml | 39 ++++++ 17 files changed, 792 insertions(+), 41 deletions(-) create mode 100644 src/tests/scenario2/.gitignore delete mode 100644 src/tests/scenario2/README.md create mode 100644 src/tests/scenario2/__init__.py create mode 100755 src/tests/scenario2/deploy_all.sh create mode 100644 src/tests/scenario2/deploy_specs_dom1.sh create mode 100644 src/tests/scenario2/deploy_specs_dom2.sh create mode 100644 src/tests/scenario2/deploy_specs_dom3.sh create mode 100644 src/tests/scenario2/deploy_specs_dom4.sh create mode 100644 src/tests/scenario2/nginx-ingress-controller-dom1.yaml create mode 100644 src/tests/scenario2/nginx-ingress-controller-dom2.yaml create mode 100644 src/tests/scenario2/nginx-ingress-controller-dom3.yaml create mode 100644 src/tests/scenario2/nginx-ingress-controller-dom4.yaml create mode 100644 src/tests/scenario2/tfs-ingress-dom1.yaml create mode 100644 src/tests/scenario2/tfs-ingress-dom2.yaml create mode 100644 src/tests/scenario2/tfs-ingress-dom3.yaml create mode 100644 src/tests/scenario2/tfs-ingress-dom4.yaml diff --git a/manifests/webuiservice.yaml b/manifests/webuiservice.yaml index cac64a816..fe08d37ae 100644 --- a/manifests/webuiservice.yaml +++ b/manifests/webuiservice.yaml @@ -60,43 +60,43 @@ spec: limits: cpu: 700m memory: 1024Mi - - name: grafana - image: grafana/grafana:8.5.11 - imagePullPolicy: IfNotPresent - ports: - - containerPort: 3000 - name: http-grafana - protocol: TCP - env: - - name: GF_SERVER_ROOT_URL - value: "http://0.0.0.0:3000/grafana/" - - name: GF_SERVER_SERVE_FROM_SUB_PATH - value: "true" - readinessProbe: - failureThreshold: 3 - httpGet: - path: /robots.txt - port: 3000 - scheme: HTTP - initialDelaySeconds: 10 - periodSeconds: 30 - successThreshold: 1 - timeoutSeconds: 2 - livenessProbe: - failureThreshold: 3 - initialDelaySeconds: 30 - periodSeconds: 10 - successThreshold: 1 - tcpSocket: - port: 3000 - timeoutSeconds: 1 - resources: - requests: - cpu: 250m - memory: 750Mi - limits: - cpu: 700m - memory: 1024Mi +# - name: grafana +# image: grafana/grafana:8.5.11 +# imagePullPolicy: IfNotPresent +# ports: +# - containerPort: 3000 +# name: http-grafana +# protocol: TCP +# env: +# - name: GF_SERVER_ROOT_URL +# value: "http://0.0.0.0:3000/grafana/" +# - name: GF_SERVER_SERVE_FROM_SUB_PATH +# value: "true" +# readinessProbe: +# failureThreshold: 3 +# httpGet: +# path: /robots.txt +# port: 3000 +# scheme: HTTP +# initialDelaySeconds: 10 +# periodSeconds: 30 +# successThreshold: 1 +# timeoutSeconds: 2 +# livenessProbe: +# failureThreshold: 3 +# initialDelaySeconds: 30 +# periodSeconds: 10 +# successThreshold: 1 +# tcpSocket: +# port: 3000 +# timeoutSeconds: 1 +# resources: +# requests: +# cpu: 250m +# memory: 750Mi +# limits: +# cpu: 700m +# memory: 1024Mi --- apiVersion: v1 kind: Service @@ -110,6 +110,6 @@ spec: - name: webui port: 8004 targetPort: 8004 - - name: grafana - port: 3000 - targetPort: 3000 +# - name: grafana +# port: 3000 +# targetPort: 3000 diff --git a/src/tests/scenario2/.gitignore b/src/tests/scenario2/.gitignore new file mode 100644 index 000000000..0a3f4400d --- /dev/null +++ b/src/tests/scenario2/.gitignore @@ -0,0 +1,2 @@ +# Add here your files containing confidential testbed details such as IP addresses, ports, usernames, passwords, etc. +descriptors_real.json diff --git a/src/tests/scenario2/README.md b/src/tests/scenario2/README.md deleted file mode 100644 index 923324c58..000000000 --- a/src/tests/scenario2/README.md +++ /dev/null @@ -1 +0,0 @@ -# Scenario 2 - ... diff --git a/src/tests/scenario2/__init__.py b/src/tests/scenario2/__init__.py new file mode 100644 index 000000000..70a332512 --- /dev/null +++ b/src/tests/scenario2/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# 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/scenario2/deploy_all.sh b/src/tests/scenario2/deploy_all.sh new file mode 100755 index 000000000..393a85351 --- /dev/null +++ b/src/tests/scenario2/deploy_all.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Delete old namespaces +kubectl delete namespace tfs-dom1 tfs-dom2 tfs-dom3 tfs-dom4 + +# Delete secondary ingress controllers +kubectl delete -f nfvsdn22/nginx-ingress-controller-dom1.yaml +kubectl delete -f nfvsdn22/nginx-ingress-controller-dom2.yaml +kubectl delete -f nfvsdn22/nginx-ingress-controller-dom3.yaml +kubectl delete -f nfvsdn22/nginx-ingress-controller-dom4.yaml + +# Create secondary ingress controllers +kubectl apply -f nfvsdn22/nginx-ingress-controller-dom1.yaml +kubectl apply -f nfvsdn22/nginx-ingress-controller-dom2.yaml +kubectl apply -f nfvsdn22/nginx-ingress-controller-dom3.yaml +kubectl apply -f nfvsdn22/nginx-ingress-controller-dom4.yaml + +# Deploy TFS for Domain 1 +source nfvsdn22/deploy_specs_dom1.sh +./deploy.sh + +# Deploy TFS for Domain 2 +source nfvsdn22/deploy_specs_dom2.sh +./deploy.sh + +# Deploy TFS for Domain 3 +source nfvsdn22/deploy_specs_dom3.sh +./deploy.sh + +# Deploy TFS for Domain 4 +source nfvsdn22/deploy_specs_dom4.sh +./deploy.sh diff --git a/src/tests/scenario2/deploy_specs_dom1.sh b/src/tests/scenario2/deploy_specs_dom1.sh new file mode 100644 index 000000000..5fd42dfcc --- /dev/null +++ b/src/tests/scenario2/deploy_specs_dom1.sh @@ -0,0 +1,17 @@ +# Set the URL of your local Docker registry where the images will be uploaded to. +export TFS_REGISTRY_IMAGE="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 webui" + +# Set the tag you want to use for your images. +export TFS_IMAGE_TAG="dev" + +# Set the name of the Kubernetes namespace to deploy to. +export TFS_K8S_NAMESPACE="tfs-dom1" + +# Set additional manifest files to be applied after the deployment +export TFS_EXTRA_MANIFESTS="nfvsdn22/tfs-ingress-dom1.yaml" + +# Set the neew Grafana admin password +export TFS_GRAFANA_PASSWORD="admin123+" diff --git a/src/tests/scenario2/deploy_specs_dom2.sh b/src/tests/scenario2/deploy_specs_dom2.sh new file mode 100644 index 000000000..341da4bbd --- /dev/null +++ b/src/tests/scenario2/deploy_specs_dom2.sh @@ -0,0 +1,17 @@ +# Set the URL of your local Docker registry where the images will be uploaded to. +export TFS_REGISTRY_IMAGE="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 webui" + +# Set the tag you want to use for your images. +export TFS_IMAGE_TAG="dev" + +# Set the name of the Kubernetes namespace to deploy to. +export TFS_K8S_NAMESPACE="tfs-dom2" + +# Set additional manifest files to be applied after the deployment +export TFS_EXTRA_MANIFESTS="nfvsdn22/tfs-ingress-dom2.yaml" + +# Set the neew Grafana admin password +export TFS_GRAFANA_PASSWORD="admin123+" diff --git a/src/tests/scenario2/deploy_specs_dom3.sh b/src/tests/scenario2/deploy_specs_dom3.sh new file mode 100644 index 000000000..60560ed99 --- /dev/null +++ b/src/tests/scenario2/deploy_specs_dom3.sh @@ -0,0 +1,17 @@ +# Set the URL of your local Docker registry where the images will be uploaded to. +export TFS_REGISTRY_IMAGE="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 webui" + +# Set the tag you want to use for your images. +export TFS_IMAGE_TAG="dev" + +# Set the name of the Kubernetes namespace to deploy to. +export TFS_K8S_NAMESPACE="tfs-dom3" + +# Set additional manifest files to be applied after the deployment +export TFS_EXTRA_MANIFESTS="nfvsdn22/tfs-ingress-dom3.yaml" + +# Set the neew Grafana admin password +export TFS_GRAFANA_PASSWORD="admin123+" diff --git a/src/tests/scenario2/deploy_specs_dom4.sh b/src/tests/scenario2/deploy_specs_dom4.sh new file mode 100644 index 000000000..ae1d4a73c --- /dev/null +++ b/src/tests/scenario2/deploy_specs_dom4.sh @@ -0,0 +1,17 @@ +# Set the URL of your local Docker registry where the images will be uploaded to. +export TFS_REGISTRY_IMAGE="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 webui" + +# Set the tag you want to use for your images. +export TFS_IMAGE_TAG="dev" + +# Set the name of the Kubernetes namespace to deploy to. +export TFS_K8S_NAMESPACE="tfs-dom4" + +# Set additional manifest files to be applied after the deployment +export TFS_EXTRA_MANIFESTS="nfvsdn22/tfs-ingress-dom4.yaml" + +# Set the neew Grafana admin password +export TFS_GRAFANA_PASSWORD="admin123+" diff --git a/src/tests/scenario2/nginx-ingress-controller-dom1.yaml b/src/tests/scenario2/nginx-ingress-controller-dom1.yaml new file mode 100644 index 000000000..1aa1ba48b --- /dev/null +++ b/src/tests/scenario2/nginx-ingress-controller-dom1.yaml @@ -0,0 +1,120 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-load-balancer-microk8s-conf-dom1 + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-udp-microk8s-conf-dom1 + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-tcp-microk8s-conf-dom1 + namespace: ingress +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: tfs-ingress-class-dom1 + annotations: + ingressclass.kubernetes.io/is-default-class: "false" +spec: + controller: tfs.etsi.org/controller-class-dom1 +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nginx-ingress-microk8s-controller-dom1 + namespace: ingress + labels: + microk8s-application: nginx-ingress-microk8s-dom1 +spec: + selector: + matchLabels: + name: nginx-ingress-microk8s-dom1 + updateStrategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + name: nginx-ingress-microk8s-dom1 + 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 + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-dom1 + - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-dom1 + - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-dom1 + - --election-id=ingress-controller-leader-dom1 + - --controller-class=tfs.etsi.org/controller-class-dom1 + - --ingress-class=tfs-ingress-class-dom1 + - ' ' + - --publish-status-address=127.0.0.1 diff --git a/src/tests/scenario2/nginx-ingress-controller-dom2.yaml b/src/tests/scenario2/nginx-ingress-controller-dom2.yaml new file mode 100644 index 000000000..2dac1ecd2 --- /dev/null +++ b/src/tests/scenario2/nginx-ingress-controller-dom2.yaml @@ -0,0 +1,120 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-load-balancer-microk8s-conf-dom2 + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-udp-microk8s-conf-dom2 + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-tcp-microk8s-conf-dom2 + namespace: ingress +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: tfs-ingress-class-dom2 + annotations: + ingressclass.kubernetes.io/is-default-class: "false" +spec: + controller: tfs.etsi.org/controller-class-dom2 +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nginx-ingress-microk8s-controller-dom2 + namespace: ingress + labels: + microk8s-application: nginx-ingress-microk8s-dom2 +spec: + selector: + matchLabels: + name: nginx-ingress-microk8s-dom2 + updateStrategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + name: nginx-ingress-microk8s-dom2 + 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 + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-dom2 + - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-dom2 + - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-dom2 + - --election-id=ingress-controller-leader-dom2 + - --controller-class=tfs.etsi.org/controller-class-dom2 + - --ingress-class=tfs-ingress-class-dom2 + - ' ' + - --publish-status-address=127.0.0.1 diff --git a/src/tests/scenario2/nginx-ingress-controller-dom3.yaml b/src/tests/scenario2/nginx-ingress-controller-dom3.yaml new file mode 100644 index 000000000..06eb6b753 --- /dev/null +++ b/src/tests/scenario2/nginx-ingress-controller-dom3.yaml @@ -0,0 +1,120 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-load-balancer-microk8s-conf-dom3 + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-udp-microk8s-conf-dom3 + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-tcp-microk8s-conf-dom3 + namespace: ingress +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: tfs-ingress-class-dom3 + annotations: + ingressclass.kubernetes.io/is-default-class: "false" +spec: + controller: tfs.etsi.org/controller-class-dom3 +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nginx-ingress-microk8s-controller-dom3 + namespace: ingress + labels: + microk8s-application: nginx-ingress-microk8s-dom3 +spec: + selector: + matchLabels: + name: nginx-ingress-microk8s-dom3 + updateStrategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + name: nginx-ingress-microk8s-dom3 + 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 + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-dom3 + - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-dom3 + - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-dom3 + - --election-id=ingress-controller-leader-dom3 + - --controller-class=tfs.etsi.org/controller-class-dom3 + - --ingress-class=tfs-ingress-class-dom3 + - ' ' + - --publish-status-address=127.0.0.1 diff --git a/src/tests/scenario2/nginx-ingress-controller-dom4.yaml b/src/tests/scenario2/nginx-ingress-controller-dom4.yaml new file mode 100644 index 000000000..c5c2e2f70 --- /dev/null +++ b/src/tests/scenario2/nginx-ingress-controller-dom4.yaml @@ -0,0 +1,120 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-load-balancer-microk8s-conf-dom4 + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-udp-microk8s-conf-dom4 + namespace: ingress +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-ingress-tcp-microk8s-conf-dom4 + namespace: ingress +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: tfs-ingress-class-dom4 + annotations: + ingressclass.kubernetes.io/is-default-class: "false" +spec: + controller: tfs.etsi.org/controller-class-dom4 +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nginx-ingress-microk8s-controller-dom4 + namespace: ingress + labels: + microk8s-application: nginx-ingress-microk8s-dom4 +spec: + selector: + matchLabels: + name: nginx-ingress-microk8s-dom4 + updateStrategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + name: nginx-ingress-microk8s-dom4 + 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: 8004 + protocol: TCP + - name: https + containerPort: 443 + hostPort: 4434 + protocol: TCP + - name: health + containerPort: 10254 + hostPort: 12544 + protocol: TCP + args: + - /nginx-ingress-controller + - --configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf-dom4 + - --tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf-dom4 + - --udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf-dom4 + - --election-id=ingress-controller-leader-dom4 + - --controller-class=tfs.etsi.org/controller-class-dom4 + - --ingress-class=tfs-ingress-class-dom4 + - ' ' + - --publish-status-address=127.0.0.1 diff --git a/src/tests/scenario2/tfs-ingress-dom1.yaml b/src/tests/scenario2/tfs-ingress-dom1.yaml new file mode 100644 index 000000000..bf2e40352 --- /dev/null +++ b/src/tests/scenario2/tfs-ingress-dom1.yaml @@ -0,0 +1,39 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: tfs-ingress-dom1 + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: tfs-ingress-class-dom1 + 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: /context(/|$)(.*) + pathType: Prefix + backend: + service: + name: contextservice + port: + number: 8080 + - path: /()(restconf/.*) + pathType: Prefix + backend: + service: + name: computeservice + port: + number: 8080 diff --git a/src/tests/scenario2/tfs-ingress-dom2.yaml b/src/tests/scenario2/tfs-ingress-dom2.yaml new file mode 100644 index 000000000..40d9480d7 --- /dev/null +++ b/src/tests/scenario2/tfs-ingress-dom2.yaml @@ -0,0 +1,39 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: tfs-ingress-dom2 + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: tfs-ingress-class-dom2 + 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: /context(/|$)(.*) + pathType: Prefix + backend: + service: + name: contextservice + port: + number: 8080 + - path: /()(restconf/.*) + pathType: Prefix + backend: + service: + name: computeservice + port: + number: 8080 diff --git a/src/tests/scenario2/tfs-ingress-dom3.yaml b/src/tests/scenario2/tfs-ingress-dom3.yaml new file mode 100644 index 000000000..28668b424 --- /dev/null +++ b/src/tests/scenario2/tfs-ingress-dom3.yaml @@ -0,0 +1,39 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: tfs-ingress-dom3 + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: tfs-ingress-class-dom3 + 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: /context(/|$)(.*) + pathType: Prefix + backend: + service: + name: contextservice + port: + number: 8080 + - path: /()(restconf/.*) + pathType: Prefix + backend: + service: + name: computeservice + port: + number: 8080 diff --git a/src/tests/scenario2/tfs-ingress-dom4.yaml b/src/tests/scenario2/tfs-ingress-dom4.yaml new file mode 100644 index 000000000..3774c327c --- /dev/null +++ b/src/tests/scenario2/tfs-ingress-dom4.yaml @@ -0,0 +1,39 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: tfs-ingress-dom4 + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: tfs-ingress-class-dom4 + 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: /context(/|$)(.*) + pathType: Prefix + backend: + service: + name: contextservice + port: + number: 8080 + - path: /()(restconf/.*) + pathType: Prefix + backend: + service: + name: computeservice + port: + number: 8080 -- GitLab From dd88c5d28e9c00eefa148729ca4c63db73b5b5cb Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 26 Oct 2022 13:34:44 +0000 Subject: [PATCH 03/55] NFV-SDN'22: - added file with notes and commands --- .../scenario2/MultiIngressController.txt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/tests/scenario2/MultiIngressController.txt diff --git a/src/tests/scenario2/MultiIngressController.txt b/src/tests/scenario2/MultiIngressController.txt new file mode 100644 index 000000000..b2d6d3224 --- /dev/null +++ b/src/tests/scenario2/MultiIngressController.txt @@ -0,0 +1,35 @@ +# Ref: https://kubernetes.github.io/ingress-nginx/user-guide/multiple-ingress/ +# Ref: https://fabianlee.org/2021/07/29/kubernetes-microk8s-with-multiple-metallb-endpoints-and-nginx-ingress-controllers/ + +# Check node limits +kubectl describe nodes + +# Create secondary ingress controllers +kubectl apply -f nfvsdn22/nginx-ingress-controller-dom1.yaml +kubectl apply -f nfvsdn22/nginx-ingress-controller-dom2.yaml +kubectl apply -f nfvsdn22/nginx-ingress-controller-dom3.yaml +kubectl apply -f nfvsdn22/nginx-ingress-controller-dom4.yaml + +# Delete secondary ingress controllers +kubectl delete -f nfvsdn22/nginx-ingress-controller-dom1.yaml +kubectl delete -f nfvsdn22/nginx-ingress-controller-dom2.yaml +kubectl delete -f nfvsdn22/nginx-ingress-controller-dom3.yaml +kubectl delete -f nfvsdn22/nginx-ingress-controller-dom4.yaml + +source nfvsdn22/deploy_specs_dom1.sh +./deploy.sh + +source nfvsdn22/deploy_specs_dom2.sh +./deploy.sh + +source nfvsdn22/deploy_specs_dom3.sh +./deploy.sh + +source nfvsdn22/deploy_specs_dom4.sh +./deploy.sh + +# Manually deploy ingresses for domains +kubectl --namespace tfs-dom1 apply -f nfvsdn22/tfs-ingress-dom1.yaml +kubectl --namespace tfs-dom2 apply -f nfvsdn22/tfs-ingress-dom2.yaml +kubectl --namespace tfs-dom3 apply -f nfvsdn22/tfs-ingress-dom3.yaml +kubectl --namespace tfs-dom4 apply -f nfvsdn22/tfs-ingress-dom4.yaml -- GitLab From 1570c79eed2b5ba1ff31b1b6276be0b5ea082861 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 26 Oct 2022 16:41:08 +0000 Subject: [PATCH 04/55] NFV-SDN'22: - added descriptor files --- src/tests/scenario2/descriptors/domain1.json | 131 +++++++++++++++++ src/tests/scenario2/descriptors/domain2.json | 147 +++++++++++++++++++ src/tests/scenario2/descriptors/domain3.json | 97 ++++++++++++ src/tests/scenario2/descriptors/domain4.json | 89 +++++++++++ 4 files changed, 464 insertions(+) create mode 100644 src/tests/scenario2/descriptors/domain1.json create mode 100644 src/tests/scenario2/descriptors/domain2.json create mode 100644 src/tests/scenario2/descriptors/domain3.json create mode 100644 src/tests/scenario2/descriptors/domain4.json diff --git a/src/tests/scenario2/descriptors/domain1.json b/src/tests/scenario2/descriptors/domain1.json new file mode 100644 index 000000000..94a60d8c6 --- /dev/null +++ b/src/tests/scenario2/descriptors/domain1.json @@ -0,0 +1,131 @@ +{ + "contexts": [ + { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "topology_ids": [], "service_ids": [] + } + ], + "topologies": [ + { + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}, + "device_ids": [], "link_ids": [] + } + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "DC1-GW@D1"}}, "device_type": "emu-datacenter", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "eth1"}, + {"sample_types": [], "type": "copper", "uuid": "int"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R1@D1"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "5"}, + {"sample_types": [], "type": "copper", "uuid": "100"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R2@D1"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "1"}, + {"sample_types": [], "type": "copper", "uuid": "3"}, + {"sample_types": [], "type": "copper", "uuid": "5"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R3@D1"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "4"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R4@D1"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "3"}, + {"sample_types": [], "type": "copper", "uuid": "5"}, + {"sample_types": [], "type": "copper", "uuid": "10"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R5@D1"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "1"}, + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "4"}, + {"sample_types": [], "type": "copper", "uuid": "10"} + ]}}} + ]} + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "DC1-GW@D1/eth1==R1@D1/100"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "DC1-GW@D1"}}, "endpoint_uuid": {"uuid": "eth1"}}, + {"device_id": {"device_uuid": {"uuid": "R1@D1"}}, "endpoint_uuid": {"uuid": "100"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R1@D1/2==R2@D1/1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1@D1"}}, "endpoint_uuid": {"uuid": "2"}}, + {"device_id": {"device_uuid": {"uuid": "R2@D1"}}, "endpoint_uuid": {"uuid": "1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2@D1/3==R3@D1/2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2@D1"}}, "endpoint_uuid": {"uuid": "3"}}, + {"device_id": {"device_uuid": {"uuid": "R3@D1"}}, "endpoint_uuid": {"uuid": "2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2@D1/5==R5@D1/2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2@D1"}}, "endpoint_uuid": {"uuid": "5"}}, + {"device_id": {"device_uuid": {"uuid": "R5@D1"}}, "endpoint_uuid": {"uuid": "2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R3@D1/4==R4@D1/3"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R3@D1"}}, "endpoint_uuid": {"uuid": "4"}}, + {"device_id": {"device_uuid": {"uuid": "R4@D1"}}, "endpoint_uuid": {"uuid": "3"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R4@D1/5==R5@D1/4"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R4@D1"}}, "endpoint_uuid": {"uuid": "5"}}, + {"device_id": {"device_uuid": {"uuid": "R5@D1"}}, "endpoint_uuid": {"uuid": "4"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R5@D1/1==R1@D1/5"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R5@D1"}}, "endpoint_uuid": {"uuid": "1"}}, + {"device_id": {"device_uuid": {"uuid": "R1@D1"}}, "endpoint_uuid": {"uuid": "5"}} + ] + } + ] +} diff --git a/src/tests/scenario2/descriptors/domain2.json b/src/tests/scenario2/descriptors/domain2.json new file mode 100644 index 000000000..2fdd3b9e5 --- /dev/null +++ b/src/tests/scenario2/descriptors/domain2.json @@ -0,0 +1,147 @@ +{ + "contexts": [ + { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "topology_ids": [], "service_ids": [] + } + ], + "topologies": [ + { + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}, + "device_ids": [], "link_ids": [] + } + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "R1@D2"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "5"}, + {"sample_types": [], "type": "copper", "uuid": "6"}, + {"sample_types": [], "type": "copper", "uuid": "10"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R2@D2"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "1"}, + {"sample_types": [], "type": "copper", "uuid": "3"}, + {"sample_types": [], "type": "copper", "uuid": "4"}, + {"sample_types": [], "type": "copper", "uuid": "5"}, + {"sample_types": [], "type": "copper", "uuid": "6"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R3@D2"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "6"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R4@D2"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "5"}, + {"sample_types": [], "type": "copper", "uuid": "10"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R5@D2"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "1"}, + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "4"}, + {"sample_types": [], "type": "copper", "uuid": "10"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R6@D2"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "1"}, + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "3"} + ]}}} + ]} + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "R1@D2/2==R2@D2/1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1@D2"}}, "endpoint_uuid": {"uuid": "2"}}, + {"device_id": {"device_uuid": {"uuid": "R2@D2"}}, "endpoint_uuid": {"uuid": "1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R1@D2/6==R6@D2/1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1@D2"}}, "endpoint_uuid": {"uuid": "6"}}, + {"device_id": {"device_uuid": {"uuid": "R6@D2"}}, "endpoint_uuid": {"uuid": "1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R1@D2/5==R5@D2/1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1@D2"}}, "endpoint_uuid": {"uuid": "5"}}, + {"device_id": {"device_uuid": {"uuid": "R5@D2"}}, "endpoint_uuid": {"uuid": "1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2@D2/3==R3@D2/2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2@D2"}}, "endpoint_uuid": {"uuid": "3"}}, + {"device_id": {"device_uuid": {"uuid": "R3@D2"}}, "endpoint_uuid": {"uuid": "2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2@D2/4==R4@D2/2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2@D2"}}, "endpoint_uuid": {"uuid": "4"}}, + {"device_id": {"device_uuid": {"uuid": "R4@D2"}}, "endpoint_uuid": {"uuid": "2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2@D2/5==R5@D2/2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2@D2"}}, "endpoint_uuid": {"uuid": "5"}}, + {"device_id": {"device_uuid": {"uuid": "R5@D2"}}, "endpoint_uuid": {"uuid": "2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2@D2/6==R6@D2/2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2@D2"}}, "endpoint_uuid": {"uuid": "6"}}, + {"device_id": {"device_uuid": {"uuid": "R6@D2"}}, "endpoint_uuid": {"uuid": "2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R3@D2/6==R6@D2/3"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R3@D2"}}, "endpoint_uuid": {"uuid": "6"}}, + {"device_id": {"device_uuid": {"uuid": "R6@D2"}}, "endpoint_uuid": {"uuid": "3"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R4@D2/5==R5@D2/4"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R4@D2"}}, "endpoint_uuid": {"uuid": "5"}}, + {"device_id": {"device_uuid": {"uuid": "R5@D2"}}, "endpoint_uuid": {"uuid": "4"}} + ] + } + ] +} diff --git a/src/tests/scenario2/descriptors/domain3.json b/src/tests/scenario2/descriptors/domain3.json new file mode 100644 index 000000000..aa61145ba --- /dev/null +++ b/src/tests/scenario2/descriptors/domain3.json @@ -0,0 +1,97 @@ +{ + "contexts": [ + { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "topology_ids": [], "service_ids": [] + } + ], + "topologies": [ + { + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}, + "device_ids": [], "link_ids": [] + } + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "R1@D3"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "4"}, + {"sample_types": [], "type": "copper", "uuid": "10"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R2@D3"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "1"}, + {"sample_types": [], "type": "copper", "uuid": "3"}, + {"sample_types": [], "type": "copper", "uuid": "4"}, + {"sample_types": [], "type": "copper", "uuid": "10"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R3@D3"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "4"}, + {"sample_types": [], "type": "copper", "uuid": "10"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R4@D3"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "1"}, + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "3"} + ]}}} + ]} + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "R1@D3/2==R2@D3/1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1@D3"}}, "endpoint_uuid": {"uuid": "2"}}, + {"device_id": {"device_uuid": {"uuid": "R2@D3"}}, "endpoint_uuid": {"uuid": "1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2@D3/3==R3@D3/2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2@D3"}}, "endpoint_uuid": {"uuid": "3"}}, + {"device_id": {"device_uuid": {"uuid": "R3@D3"}}, "endpoint_uuid": {"uuid": "2"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R3@D3/4==R4@D3/3"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R3@D3"}}, "endpoint_uuid": {"uuid": "4"}}, + {"device_id": {"device_uuid": {"uuid": "R4@D3"}}, "endpoint_uuid": {"uuid": "3"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R4@D3/1==R1@D3/4"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R4@D3"}}, "endpoint_uuid": {"uuid": "1"}}, + {"device_id": {"device_uuid": {"uuid": "R1@D3"}}, "endpoint_uuid": {"uuid": "4"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2@D3/4==R4@D3/2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2@D3"}}, "endpoint_uuid": {"uuid": "4"}}, + {"device_id": {"device_uuid": {"uuid": "R4@D3"}}, "endpoint_uuid": {"uuid": "2"}} + ] + } + ] +} diff --git a/src/tests/scenario2/descriptors/domain4.json b/src/tests/scenario2/descriptors/domain4.json new file mode 100644 index 000000000..ffafde7c5 --- /dev/null +++ b/src/tests/scenario2/descriptors/domain4.json @@ -0,0 +1,89 @@ +{ + "contexts": [ + { + "context_id": {"context_uuid": {"uuid": "admin"}}, + "topology_ids": [], "service_ids": [] + } + ], + "topologies": [ + { + "topology_id": {"context_id": {"context_uuid": {"uuid": "admin"}}, "topology_uuid": {"uuid": "admin"}}, + "device_ids": [], "link_ids": [] + } + ], + "devices": [ + { + "device_id": {"device_uuid": {"uuid": "DC2-GW@D4"}}, "device_type": "emu-datacenter", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "eth1"}, + {"sample_types": [], "type": "copper", "uuid": "int"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R1@D4"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "3"}, + {"sample_types": [], "type": "copper", "uuid": "10"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R2@D4"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "1"}, + {"sample_types": [], "type": "copper", "uuid": "3"}, + {"sample_types": [], "type": "copper", "uuid": "10"} + ]}}} + ]} + }, + { + "device_id": {"device_uuid": {"uuid": "R3@D4"}}, "device_type": "emu-packet-router", "device_drivers": [0], + "device_endpoints": [], "device_operational_status": 1, "device_config": {"config_rules": [ + {"action": 1, "custom": {"resource_key": "_connect/address", "resource_value": "127.0.0.1"}}, + {"action": 1, "custom": {"resource_key": "_connect/port", "resource_value": "0"}}, + {"action": 1, "custom": {"resource_key": "_connect/settings", "resource_value": {"endpoints": [ + {"sample_types": [], "type": "copper", "uuid": "1"}, + {"sample_types": [], "type": "copper", "uuid": "2"}, + {"sample_types": [], "type": "copper", "uuid": "100"} + ]}}} + ]} + } + ], + "links": [ + { + "link_id": {"link_uuid": {"uuid": "R3@D4/100==DC2-GW@D4/eth1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "DC2-GW@D4"}}, "endpoint_uuid": {"uuid": "eth1"}}, + {"device_id": {"device_uuid": {"uuid": "R3@D4"}}, "endpoint_uuid": {"uuid": "100"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R1@D4/2==R2@D4/1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1@D4"}}, "endpoint_uuid": {"uuid": "2"}}, + {"device_id": {"device_uuid": {"uuid": "R2@D4"}}, "endpoint_uuid": {"uuid": "1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R1@D4/3==R3@D4/1"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R1@D4"}}, "endpoint_uuid": {"uuid": "3"}}, + {"device_id": {"device_uuid": {"uuid": "R3@D4"}}, "endpoint_uuid": {"uuid": "1"}} + ] + }, + { + "link_id": {"link_uuid": {"uuid": "R2@D4/3==R3@D4/2"}}, "link_endpoint_ids": [ + {"device_id": {"device_uuid": {"uuid": "R2@D4"}}, "endpoint_uuid": {"uuid": "3"}}, + {"device_id": {"device_uuid": {"uuid": "R3@D4"}}, "endpoint_uuid": {"uuid": "2"}} + ] + } + ] +} -- GitLab From d8dea67c0ad3aa56b71268642814258f33337ad0 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Wed, 26 Oct 2022 16:41:35 +0000 Subject: [PATCH 05/55] Common & WebUI: - added device type "NETWORK" to represent an abstracted network --- src/common/DeviceTypes.py | 3 +++ .../topology_icons/{cloud.png => network.png} | Bin 2 files changed, 3 insertions(+) rename src/webui/service/static/topology_icons/{cloud.png => network.png} (100%) diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py index 08f18dd40..3950586ed 100644 --- a/src/common/DeviceTypes.py +++ b/src/common/DeviceTypes.py @@ -16,6 +16,9 @@ from enum import Enum class DeviceTypeEnum(Enum): + # Abstractions + NETWORK = 'network' + # Emulated device types EMULATED_DATACENTER = 'emu-datacenter' EMULATED_MICROVAWE_RADIO_SYSTEM = 'emu-microwave-radio-system' diff --git a/src/webui/service/static/topology_icons/cloud.png b/src/webui/service/static/topology_icons/network.png similarity index 100% rename from src/webui/service/static/topology_icons/cloud.png rename to src/webui/service/static/topology_icons/network.png -- GitLab From 740dbcf73516886ad142dd0a270f4ae214041231 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Thu, 27 Oct 2022 17:04:18 +0000 Subject: [PATCH 06/55] NFV-SDN'22 scenario - Added MockBlockchain deployment - Added interdomain and DLT components per domain. --- src/tests/scenario2/deploy_all.sh | 6 ++++++ src/tests/scenario2/deploy_specs_dom1.sh | 2 +- src/tests/scenario2/deploy_specs_dom2.sh | 2 +- src/tests/scenario2/deploy_specs_dom3.sh | 2 +- src/tests/scenario2/deploy_specs_dom4.sh | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/tests/scenario2/deploy_all.sh b/src/tests/scenario2/deploy_all.sh index 393a85351..b1563ad3b 100755 --- a/src/tests/scenario2/deploy_all.sh +++ b/src/tests/scenario2/deploy_all.sh @@ -9,12 +9,18 @@ kubectl delete -f nfvsdn22/nginx-ingress-controller-dom2.yaml kubectl delete -f nfvsdn22/nginx-ingress-controller-dom3.yaml kubectl delete -f nfvsdn22/nginx-ingress-controller-dom4.yaml +# Delete MockBlockchain +kubectl delete namespace tfs-bchain + # Create secondary ingress controllers kubectl apply -f nfvsdn22/nginx-ingress-controller-dom1.yaml kubectl apply -f nfvsdn22/nginx-ingress-controller-dom2.yaml kubectl apply -f nfvsdn22/nginx-ingress-controller-dom3.yaml kubectl apply -f nfvsdn22/nginx-ingress-controller-dom4.yaml +# Create MockBlockchain +./deploy_mock_blockchain.sh + # Deploy TFS for Domain 1 source nfvsdn22/deploy_specs_dom1.sh ./deploy.sh diff --git a/src/tests/scenario2/deploy_specs_dom1.sh b/src/tests/scenario2/deploy_specs_dom1.sh index 5fd42dfcc..7c9fb119f 100644 --- a/src/tests/scenario2/deploy_specs_dom1.sh +++ b/src/tests/scenario2/deploy_specs_dom1.sh @@ -2,7 +2,7 @@ export TFS_REGISTRY_IMAGE="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 webui" +export TFS_COMPONENTS="context device pathcomp service slice dlt interdomain webui" # Set the tag you want to use for your images. export TFS_IMAGE_TAG="dev" diff --git a/src/tests/scenario2/deploy_specs_dom2.sh b/src/tests/scenario2/deploy_specs_dom2.sh index 341da4bbd..4972aaaf9 100644 --- a/src/tests/scenario2/deploy_specs_dom2.sh +++ b/src/tests/scenario2/deploy_specs_dom2.sh @@ -2,7 +2,7 @@ export TFS_REGISTRY_IMAGE="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 webui" +export TFS_COMPONENTS="context device pathcomp service slice dlt interdomain webui" # Set the tag you want to use for your images. export TFS_IMAGE_TAG="dev" diff --git a/src/tests/scenario2/deploy_specs_dom3.sh b/src/tests/scenario2/deploy_specs_dom3.sh index 60560ed99..7a54f93a1 100644 --- a/src/tests/scenario2/deploy_specs_dom3.sh +++ b/src/tests/scenario2/deploy_specs_dom3.sh @@ -2,7 +2,7 @@ export TFS_REGISTRY_IMAGE="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 webui" +export TFS_COMPONENTS="context device pathcomp service slice dlt interdomain webui" # Set the tag you want to use for your images. export TFS_IMAGE_TAG="dev" diff --git a/src/tests/scenario2/deploy_specs_dom4.sh b/src/tests/scenario2/deploy_specs_dom4.sh index ae1d4a73c..4fa789611 100644 --- a/src/tests/scenario2/deploy_specs_dom4.sh +++ b/src/tests/scenario2/deploy_specs_dom4.sh @@ -2,7 +2,7 @@ export TFS_REGISTRY_IMAGE="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 webui" +export TFS_COMPONENTS="context device pathcomp service slice dlt interdomain webui" # Set the tag you want to use for your images. export TFS_IMAGE_TAG="dev" -- GitLab From ae916d200d4a41e0fbec72a36e78402a52b6d5ed Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Fri, 28 Oct 2022 19:25:39 +0000 Subject: [PATCH 07/55] Manifests: - tweaked resources and containers to accelerate tests --- manifests/contextservice.yaml | 4 +-- manifests/deviceservice.yaml | 2 +- manifests/dltservice.yaml | 53 +++++++++++++++++-------------- manifests/interdomainservice.yaml | 2 +- manifests/mock_blockchain.yaml | 2 +- manifests/pathcompservice.yaml | 4 +-- manifests/serviceservice.yaml | 2 +- manifests/sliceservice.yaml | 2 +- manifests/webuiservice.yaml | 2 +- 9 files changed, 39 insertions(+), 34 deletions(-) diff --git a/manifests/contextservice.yaml b/manifests/contextservice.yaml index 04da586df..41a55e468 100644 --- a/manifests/contextservice.yaml +++ b/manifests/contextservice.yaml @@ -34,7 +34,7 @@ spec: - containerPort: 6379 resources: requests: - cpu: 250m + cpu: 200m memory: 512Mi limits: cpu: 700m @@ -64,7 +64,7 @@ spec: command: ["/bin/grpc_health_probe", "-addr=:1010"] resources: requests: - cpu: 250m + cpu: 200m memory: 512Mi limits: cpu: 700m diff --git a/manifests/deviceservice.yaml b/manifests/deviceservice.yaml index 46c7557d9..2c450c619 100644 --- a/manifests/deviceservice.yaml +++ b/manifests/deviceservice.yaml @@ -43,7 +43,7 @@ spec: command: ["/bin/grpc_health_probe", "-addr=:2020"] resources: requests: - cpu: 250m + cpu: 200m memory: 512Mi limits: cpu: 700m diff --git a/manifests/dltservice.yaml b/manifests/dltservice.yaml index 5ef6eae7d..7e00bd150 100644 --- a/manifests/dltservice.yaml +++ b/manifests/dltservice.yaml @@ -34,7 +34,12 @@ spec: - containerPort: 8080 env: - name: LOG_LEVEL - value: "INFO" + value: "DEBUG" + # for debug purposes + - name: DLT_GATEWAY_HOST + value: "mock-blockchain.tfs-bchain.svc.cluster.local" + - name: DLT_GATEWAY_PORT + value: "50051" readinessProbe: exec: command: ["/bin/grpc_health_probe", "-addr=:8080"] @@ -43,33 +48,33 @@ spec: command: ["/bin/grpc_health_probe", "-addr=:8080"] resources: requests: - cpu: 250m - memory: 512Mi - limits: - cpu: 700m - memory: 1024Mi - - name: gateway - image: registry.gitlab.com/teraflow-h2020/controller/dlt-gateway:latest - imagePullPolicy: Always - #readinessProbe: - # httpGet: - # path: /health - # port: 8081 - # initialDelaySeconds: 5 - # timeoutSeconds: 5 - #livenessProbe: - # httpGet: - # path: /health - # port: 8081 - # initialDelaySeconds: 5 - # timeoutSeconds: 5 - resources: - requests: - cpu: 250m + cpu: 200m memory: 512Mi limits: cpu: 700m memory: 1024Mi +# - name: gateway +# image: registry.gitlab.com/teraflow-h2020/controller/dlt-gateway:latest +# imagePullPolicy: Always +# #readinessProbe: +# # httpGet: +# # path: /health +# # port: 8081 +# # initialDelaySeconds: 5 +# # timeoutSeconds: 5 +# #livenessProbe: +# # httpGet: +# # path: /health +# # port: 8081 +# # initialDelaySeconds: 5 +# # timeoutSeconds: 5 +# resources: +# requests: +# cpu: 200m +# memory: 512Mi +# limits: +# cpu: 700m +# memory: 1024Mi --- apiVersion: v1 kind: Service diff --git a/manifests/interdomainservice.yaml b/manifests/interdomainservice.yaml index ca30da010..8073ff76f 100644 --- a/manifests/interdomainservice.yaml +++ b/manifests/interdomainservice.yaml @@ -43,7 +43,7 @@ spec: command: ["/bin/grpc_health_probe", "-addr=:10010"] resources: requests: - cpu: 250m + cpu: 100m memory: 512Mi limits: cpu: 700m diff --git a/manifests/mock_blockchain.yaml b/manifests/mock_blockchain.yaml index b383d7db4..1e1a1a4c6 100644 --- a/manifests/mock_blockchain.yaml +++ b/manifests/mock_blockchain.yaml @@ -43,7 +43,7 @@ spec: command: ["/bin/grpc_health_probe", "-addr=:50051"] resources: requests: - cpu: 250m + cpu: 150m memory: 512Mi limits: cpu: 700m diff --git a/manifests/pathcompservice.yaml b/manifests/pathcompservice.yaml index d5939cb15..c0f018beb 100644 --- a/manifests/pathcompservice.yaml +++ b/manifests/pathcompservice.yaml @@ -43,7 +43,7 @@ spec: command: ["/bin/grpc_health_probe", "-addr=:10020"] resources: requests: - cpu: 250m + cpu: 100m memory: 512Mi limits: cpu: 700m @@ -65,7 +65,7 @@ spec: # timeoutSeconds: 5 resources: requests: - cpu: 250m + cpu: 200m memory: 512Mi limits: cpu: 700m diff --git a/manifests/serviceservice.yaml b/manifests/serviceservice.yaml index efe43fe22..729aeb5c0 100644 --- a/manifests/serviceservice.yaml +++ b/manifests/serviceservice.yaml @@ -43,7 +43,7 @@ spec: command: ["/bin/grpc_health_probe", "-addr=:3030"] resources: requests: - cpu: 250m + cpu: 100m memory: 512Mi limits: cpu: 700m diff --git a/manifests/sliceservice.yaml b/manifests/sliceservice.yaml index eeed3776c..67af029b6 100644 --- a/manifests/sliceservice.yaml +++ b/manifests/sliceservice.yaml @@ -43,7 +43,7 @@ spec: command: ["/bin/grpc_health_probe", "-addr=:4040"] resources: requests: - cpu: 250m + cpu: 100m memory: 512Mi limits: cpu: 700m diff --git a/manifests/webuiservice.yaml b/manifests/webuiservice.yaml index fe08d37ae..fd5d17822 100644 --- a/manifests/webuiservice.yaml +++ b/manifests/webuiservice.yaml @@ -55,7 +55,7 @@ spec: timeoutSeconds: 1 resources: requests: - cpu: 250m + cpu: 100m memory: 512Mi limits: cpu: 700m -- GitLab From 9d6b4876a2a82e32f06973f251451888a3978a86 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Fri, 28 Oct 2022 19:26:28 +0000 Subject: [PATCH 08/55] MockDltGateway: - Improved error logging --- src/common/tests/MockServicerImpl_DltGateway.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/common/tests/MockServicerImpl_DltGateway.py b/src/common/tests/MockServicerImpl_DltGateway.py index 2d7501682..caef42b37 100644 --- a/src/common/tests/MockServicerImpl_DltGateway.py +++ b/src/common/tests/MockServicerImpl_DltGateway.py @@ -36,6 +36,10 @@ class AlreadyExistsException(Exception): class DoesNotExistException(Exception): pass +MSG_NOT_EXISTS = 'RecordId({:s}, {:s}, {:s}) Does Not Exist' +MSG_ALREADY_EXISTS = 'RecordId({:s}, {:s}, {:s}) Already Exists' +MSG_OPERATION_NOT_IMPLEMENTED = 'DltRecordOperationEnum({:s}) Not Implemented' + class MockServicerImpl_DltGateway(DltGatewayServiceServicer): def __init__(self): LOGGER.info('[__init__] Creating Servicer...') @@ -50,9 +54,9 @@ class MockServicerImpl_DltGateway(DltGatewayServiceServicer): records_type : Dict[str, Dict] = records_domain.setdefault(str_type, {}) record : Optional[Dict] = records_type.get(record_uuid) if should_exist and record is None: - raise DoesNotExistException('RecordId({:s}, {:s}, {:s})'.format(domain_uuid, str_type, record_uuid)) + raise DoesNotExistException(MSG_NOT_EXISTS.format(domain_uuid, str_type, record_uuid)) elif not should_exist and record is not None: - raise AlreadyExistsException('RecordId({:s}, {:s}, {:s})'.format(domain_uuid, str_type, record_uuid)) + raise AlreadyExistsException(MSG_ALREADY_EXISTS.format(domain_uuid, str_type, record_uuid)) return record def __set_record(self, record_id : DltRecordId, should_exist : bool, data_json : str) -> None: @@ -62,9 +66,9 @@ class MockServicerImpl_DltGateway(DltGatewayServiceServicer): records_type : Dict[str, Dict] = records_domain.setdefault(str_type, {}) record : Optional[Dict] = records_type.get(record_uuid) if should_exist and record is None: - raise DoesNotExistException('RecordId({:s}, {:s}, {:s})'.format(domain_uuid, str_type, record_uuid)) + raise DoesNotExistException(MSG_NOT_EXISTS.format(domain_uuid, str_type, record_uuid)) elif not should_exist and record is not None: - raise AlreadyExistsException('RecordId({:s}, {:s}, {:s})'.format(domain_uuid, str_type, record_uuid)) + raise AlreadyExistsException(MSG_ALREADY_EXISTS.format(domain_uuid, str_type, record_uuid)) records_type[record_uuid] = json.loads(data_json) def __del_record(self, record_id : DltRecordId) -> None: @@ -74,7 +78,7 @@ class MockServicerImpl_DltGateway(DltGatewayServiceServicer): records_type : Dict[str, Dict] = records_domain.setdefault(str_type, {}) record : Optional[Dict] = records_type.get(record_uuid) if record is None: - raise DoesNotExistException('RecordId({:s}, {:s}, {:s})'.format(domain_uuid, str_type, record_uuid)) + raise DoesNotExistException(MSG_NOT_EXISTS.format(domain_uuid, str_type, record_uuid)) records_type.discard(record_uuid) def __publish(self, operation : DltRecordOperationEnum, record_id : DltRecordId) -> None: @@ -106,7 +110,7 @@ class MockServicerImpl_DltGateway(DltGatewayServiceServicer): self.__del_record(record_id) else: str_operation = DltRecordOperationEnum.Name(operation).upper().replace('DLTRECORDOPERATION_', '') - raise NotImplementedError('DltRecordOperationEnum({:s})'.format(str_operation)) + raise NotImplementedError(MSG_OPERATION_NOT_IMPLEMENTED.format(str_operation)) self.__publish(operation, record_id) response.status = DLTRECORDSTATUS_SUCCEEDED except Exception as e: # pylint: disable=broad-except -- GitLab From 2444817c95a4a2cc9b64cb66a53e66768626ab9d Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Fri, 28 Oct 2022 19:27:39 +0000 Subject: [PATCH 09/55] Context EventsCollector: - redesigned EventsCollector to enable auto-reconnections --- src/context/client/EventsCollector.py | 142 +++++++++++++++----------- 1 file changed, 85 insertions(+), 57 deletions(-) diff --git a/src/context/client/EventsCollector.py b/src/context/client/EventsCollector.py index 9715098bd..f5fc3fbc7 100644 --- a/src/context/client/EventsCollector.py +++ b/src/context/client/EventsCollector.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc, logging, queue, threading +from typing import Callable +import grpc, logging, queue, threading, time from common.proto.context_pb2 import Empty from common.tools.grpc.Tools import grpc_message_to_json_string from context.client.ContextClient import ContextClient @@ -20,6 +21,41 @@ from context.client.ContextClient import ContextClient LOGGER = logging.getLogger(__name__) LOGGER.setLevel(logging.DEBUG) +class _Collector(threading.Thread): + def __init__( + self, subscription_func : Callable, events_queue = queue.Queue, + terminate = threading.Event, log_events_received: bool = False + ) -> None: + super().__init__(daemon=False) + self._subscription_func = subscription_func + self._events_queue = events_queue + self._terminate = terminate + self._log_events_received = log_events_received + self._stream = None + + def cancel(self) -> None: + if self._stream is None: return + self._stream.cancel() + + def run(self) -> None: + while not self._terminate.is_set(): + self._stream = self._subscription_func() + try: + for event in self._stream: + if self._log_events_received: + str_event = grpc_message_to_json_string(event) + LOGGER.info('[_collect] event: {:s}'.format(str_event)) + self._events_queue.put_nowait(event) + except grpc.RpcError as e: + if e.code() == grpc.StatusCode.UNAVAILABLE: + LOGGER.info('[_collect] UNAVAILABLE... retrying...') + time.sleep(0.5) + continue + elif e.code() == grpc.StatusCode.CANCELLED: + break + else: + raise # pragma: no cover + class EventsCollector: def __init__( self, context_client : ContextClient, @@ -31,60 +67,49 @@ class EventsCollector: activate_service_collector : bool = True, activate_slice_collector : bool = True, activate_connection_collector : bool = True, - ) -> None: self._events_queue = queue.Queue() + self._terminate = threading.Event() self._log_events_received = log_events_received - self._context_stream, self._context_thread = None, None - if activate_context_collector: - self._context_stream = context_client.GetContextEvents(Empty()) - self._context_thread = self._create_collector_thread(self._context_stream) - - self._topology_stream, self._topology_thread = None, None - if activate_topology_collector: - self._topology_stream = context_client.GetTopologyEvents(Empty()) - self._topology_thread = self._create_collector_thread(self._topology_stream) - - self._device_stream, self._device_thread = None, None - if activate_device_collector: - self._device_stream = context_client.GetDeviceEvents(Empty()) - self._device_thread = self._create_collector_thread(self._device_stream) - - self._link_stream, self._link_thread = None, None - if activate_link_collector: - self._link_stream = context_client.GetLinkEvents(Empty()) - self._link_thread = self._create_collector_thread(self._link_stream) - - self._service_stream, self._service_thread = None, None - if activate_service_collector: - self._service_stream = context_client.GetServiceEvents(Empty()) - self._service_thread = self._create_collector_thread(self._service_stream) - - self._slice_stream, self._slice_thread = None, None - if activate_slice_collector: - self._slice_stream = context_client.GetSliceEvents(Empty()) - self._slice_thread = self._create_collector_thread(self._slice_stream) - - self._connection_stream, self._connection_thread = None, None - if activate_connection_collector: - self._connection_stream = context_client.GetConnectionEvents(Empty()) - self._connection_thread = self._create_collector_thread(self._connection_stream) - - def _create_collector_thread(self, stream, as_daemon : bool = False): - return threading.Thread(target=self._collect, args=(stream,), daemon=as_daemon) - - def _collect(self, events_stream) -> None: - try: - for event in events_stream: - if self._log_events_received: - LOGGER.info('[_collect] event: {:s}'.format(grpc_message_to_json_string(event))) - self._events_queue.put_nowait(event) - except grpc.RpcError as e: - if e.code() != grpc.StatusCode.CANCELLED: # pylint: disable=no-member - raise # pragma: no cover + self._context_thread = _Collector( + lambda: context_client.GetContextEvents(Empty()), + self._events_queue, self._terminate, self._log_events_received + ) if activate_context_collector else None + + self._topology_thread = _Collector( + lambda: context_client.GetTopologyEvents(Empty()), + self._events_queue, self._terminate, self._log_events_received + ) if activate_topology_collector else None + + self._device_thread = _Collector( + lambda: context_client.GetDeviceEvents(Empty()), + self._events_queue, self._terminate, self._log_events_received + ) if activate_device_collector else None + + self._link_thread = _Collector( + lambda: context_client.GetLinkEvents(Empty()), + self._events_queue, self._terminate, self._log_events_received + ) if activate_link_collector else None + + self._service_thread = _Collector( + lambda: context_client.GetServiceEvents(Empty()), + self._events_queue, self._terminate, self._log_events_received + ) if activate_service_collector else None + + self._slice_thread = _Collector( + lambda: context_client.GetSliceEvents(Empty()), + self._events_queue, self._terminate, self._log_events_received + ) if activate_slice_collector else None + + self._connection_thread = _Collector( + lambda: context_client.GetConnectionEvents(Empty()), + self._events_queue, self._terminate, self._log_events_received + ) if activate_connection_collector else None def start(self): + self._terminate.clear() + if self._context_thread is not None: self._context_thread.start() if self._topology_thread is not None: self._topology_thread.start() if self._device_thread is not None: self._device_thread.start() @@ -102,25 +127,28 @@ class EventsCollector: def get_events(self, block : bool = True, timeout : float = 0.1, count : int = None): events = [] if count is None: - while True: + while not self._terminate.is_set(): event = self.get_event(block=block, timeout=timeout) if event is None: break events.append(event) else: for _ in range(count): + if self._terminate.is_set(): break event = self.get_event(block=block, timeout=timeout) if event is None: continue events.append(event) return sorted(events, key=lambda e: e.event.timestamp.timestamp) def stop(self): - if self._context_stream is not None: self._context_stream.cancel() - if self._topology_stream is not None: self._topology_stream.cancel() - if self._device_stream is not None: self._device_stream.cancel() - if self._link_stream is not None: self._link_stream.cancel() - if self._service_stream is not None: self._service_stream.cancel() - if self._slice_stream is not None: self._slice_stream.cancel() - if self._connection_stream is not None: self._connection_stream.cancel() + self._terminate.set() + + if self._context_thread is not None: self._context_thread.cancel() + if self._topology_thread is not None: self._topology_thread.cancel() + if self._device_thread is not None: self._device_thread.cancel() + if self._link_thread is not None: self._link_thread.cancel() + if self._service_thread is not None: self._service_thread.cancel() + if self._slice_thread is not None: self._slice_thread.cancel() + if self._connection_thread is not None: self._connection_thread.cancel() if self._context_thread is not None: self._context_thread.join() if self._topology_thread is not None: self._topology_thread.join() -- GitLab From 416038a7ff62c04689876ab0bc83e3e892b5cbba Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Fri, 28 Oct 2022 19:30:59 +0000 Subject: [PATCH 10/55] DLT: - Extended DltGatewayClient to support configuration through env vars - Added DltEventsCollector to DltConnector - Implemented RecordDevice in DltConnectorServicer --- src/common/Constants.py | 8 +-- src/dlt/connector/Config.py | 11 ++++ src/dlt/connector/client/DltGatewayClient.py | 7 ++- .../DltConnectorServiceServicerImpl.py | 53 ++++++++++++++++++- src/dlt/connector/service/__main__.py | 7 +++ 5 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/common/Constants.py b/src/common/Constants.py index a536ef600..1e4550fbf 100644 --- a/src/common/Constants.py +++ b/src/common/Constants.py @@ -30,8 +30,10 @@ DEFAULT_HTTP_BIND_ADDRESS = '0.0.0.0' DEFAULT_METRICS_PORT = 9192 # Default context and topology UUIDs -DEFAULT_CONTEXT_UUID = 'admin' -DEFAULT_TOPOLOGY_UUID = 'admin' +DEFAULT_CONTEXT_UUID = 'admin' +DEFAULT_TOPOLOGY_UUID = 'admin' # contains the detailed local topology +DOMAINS_TOPOLOGY_UUID = 'domains' # contains the abstracted domains (abstracted local + abstracted remotes) +AGGREGATED_TOPOLOGY_UUID = 'aggregated' # contains the aggregated view (detailed local + abstracted remotes) # Default service names class ServiceNameEnum(Enum): @@ -50,7 +52,7 @@ class ServiceNameEnum(Enum): WEBUI = 'webui' # Used for test and debugging only - DLT_GATEWAY = 'dlt-gateway' + DLT_GATEWAY = 'dltgateway' # Default gRPC service ports DEFAULT_SERVICE_GRPC_PORTS = { diff --git a/src/dlt/connector/Config.py b/src/dlt/connector/Config.py index 9953c8205..bdf9f3069 100644 --- a/src/dlt/connector/Config.py +++ b/src/dlt/connector/Config.py @@ -11,3 +11,14 @@ # 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 os + +DEFAULT_DLT_GATEWAY_HOST = '127.0.0.1' +DEFAULT_DLT_GATEWAY_PORT = '50051' + +# Find IP:port of gateway container as follows: +# - first check env vars DLT_GATEWAY_HOST & DLT_GATEWAY_PORT +# - if not set, use DEFAULT_DLT_GATEWAY_HOST & DEFAULT_DLT_GATEWAY_PORT +DLT_GATEWAY_HOST = str(os.environ.get('DLT_GATEWAY_HOST', DEFAULT_DLT_GATEWAY_HOST)) +DLT_GATEWAY_PORT = int(os.environ.get('DLT_GATEWAY_PORT', DEFAULT_DLT_GATEWAY_PORT)) diff --git a/src/dlt/connector/client/DltGatewayClient.py b/src/dlt/connector/client/DltGatewayClient.py index f1f8dec39..e2f5530f9 100644 --- a/src/dlt/connector/client/DltGatewayClient.py +++ b/src/dlt/connector/client/DltGatewayClient.py @@ -14,14 +14,13 @@ from typing import Iterator import grpc, logging -from common.Constants import ServiceNameEnum -from common.Settings import get_service_host, get_service_port_grpc from common.proto.context_pb2 import Empty, TeraFlowController from common.proto.dlt_gateway_pb2 import ( DltPeerStatus, DltPeerStatusList, DltRecord, DltRecordEvent, DltRecordId, DltRecordStatus, DltRecordSubscription) from common.proto.dlt_gateway_pb2_grpc import DltGatewayServiceStub from common.tools.client.RetryDecorator import retry, delay_exponential from common.tools.grpc.Tools import grpc_message_to_json_string +from dlt.connector.Config import DLT_GATEWAY_HOST, DLT_GATEWAY_PORT LOGGER = logging.getLogger(__name__) MAX_RETRIES = 15 @@ -30,8 +29,8 @@ RETRY_DECORATOR = retry(max_retries=MAX_RETRIES, delay_function=DELAY_FUNCTION, class DltGatewayClient: def __init__(self, host=None, port=None): - if not host: host = get_service_host(ServiceNameEnum.DLT) - if not port: port = get_service_port_grpc(ServiceNameEnum.DLT) + if not host: host = DLT_GATEWAY_HOST + if not port: port = DLT_GATEWAY_PORT self.endpoint = '{:s}:{:s}'.format(str(host), str(port)) LOGGER.debug('Creating channel to {:s}...'.format(self.endpoint)) self.channel = None diff --git a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py index 860e46f3a..2ed26b8b2 100644 --- a/src/dlt/connector/service/DltConnectorServiceServicerImpl.py +++ b/src/dlt/connector/service/DltConnectorServiceServicerImpl.py @@ -12,10 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -import grpc, logging +import copy, grpc, logging +from typing import Optional +from common.Constants import DEFAULT_CONTEXT_UUID +from common.proto.dlt_gateway_pb2 import DltRecord, DltRecordId, DltRecordOperationEnum, DltRecordTypeEnum from common.rpc_method_wrapper.Decorator import create_metrics, safe_and_metered_rpc_method from common.proto.context_pb2 import DeviceId, Empty, ServiceId, SliceId from common.proto.dlt_connector_pb2_grpc import DltConnectorServiceServicer +from common.tools.grpc.Tools import grpc_message_to_json_string +from context.client.ContextClient import ContextClient +from dlt.connector.client.DltGatewayClient import DltGatewayClient LOGGER = logging.getLogger(__name__) @@ -31,8 +37,26 @@ METRICS = create_metrics(SERVICE_NAME, METHOD_NAMES) class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): def __init__(self): LOGGER.debug('Creating Servicer...') + self._own_domain_uuid : Optional[str] = None LOGGER.debug('Servicer Created') + @property + def own_domain_id(self) -> str: + if self._own_domain_uuid is not None: return self._own_domain_uuid + + context_client = ContextClient() + existing_context_ids = context_client.ListContextIds(Empty()) + existing_context_uuids = {context_id.context_uuid.uuid for context_id in existing_context_ids.context_ids} + + # Detect local context name (will be used as abstracted device name); exclude DEFAULT_CONTEXT_UUID + existing_non_admin_context_uuids = copy.deepcopy(existing_context_uuids) + existing_non_admin_context_uuids.discard(DEFAULT_CONTEXT_UUID) + if len(existing_non_admin_context_uuids) != 1: + MSG = 'Unable to identify own domain name. Existing Contexts({:s})' + raise Exception(MSG.format(str(existing_context_uuids))) + self._own_domain_uuid = existing_non_admin_context_uuids.pop() + return self._own_domain_uuid + @safe_and_metered_rpc_method(METRICS, LOGGER) def RecordAll(self, request : Empty, context : grpc.ServicerContext) -> Empty: return Empty() @@ -43,6 +67,33 @@ class DltConnectorServiceServicerImpl(DltConnectorServiceServicer): @safe_and_metered_rpc_method(METRICS, LOGGER) def RecordDevice(self, request : DeviceId, context : grpc.ServicerContext) -> Empty: + context_client = ContextClient() + device = context_client.GetDevice(request) + + dltgateway_client = DltGatewayClient() + + dlt_record_id = DltRecordId() + dlt_record_id.domain_uuid.uuid = self.own_domain_id + dlt_record_id.type = DltRecordTypeEnum.DLTRECORDTYPE_DEVICE + dlt_record_id.record_uuid.uuid = device.device_id.device_uuid.uuid + + LOGGER.info('[RecordDevice] sent dlt_record_id = {:s}'.format(grpc_message_to_json_string(dlt_record_id))) + dlt_record = dltgateway_client.GetFromDlt(dlt_record_id) + LOGGER.info('[RecordDevice] recv dlt_record = {:s}'.format(grpc_message_to_json_string(dlt_record))) + + exists = False + + dlt_record = DltRecord() + dlt_record.record_id.CopyFrom(dlt_record_id) + dlt_record.operation = \ + DltRecordOperationEnum.DLTRECORDOPERATION_UPDATE \ + if exists else \ + DltRecordOperationEnum.DLTRECORDOPERATION_ADD + + dlt_record.data_json = grpc_message_to_json_string(device) + LOGGER.info('[RecordDevice] sent dlt_record = {:s}'.format(grpc_message_to_json_string(dlt_record))) + dlt_record_status = dltgateway_client.RecordToDlt(dlt_record) + LOGGER.info('[RecordDevice] recv dlt_record_status = {:s}'.format(grpc_message_to_json_string(dlt_record_status))) return Empty() @safe_and_metered_rpc_method(METRICS, LOGGER) diff --git a/src/dlt/connector/service/__main__.py b/src/dlt/connector/service/__main__.py index 435a93f61..e7c3841e9 100644 --- a/src/dlt/connector/service/__main__.py +++ b/src/dlt/connector/service/__main__.py @@ -18,6 +18,8 @@ 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 dlt.connector.client.DltEventsCollector import DltEventsCollector +from dlt.connector.client.DltGatewayClient import DltGatewayClient from .DltConnectorService import DltConnectorService terminate = threading.Event() @@ -48,6 +50,10 @@ def main(): metrics_port = get_metrics_port() start_http_server(metrics_port) + dlt_gateway_client = DltGatewayClient() + dlt_events_collector = DltEventsCollector(dlt_gateway_client, log_events_received=True) + dlt_events_collector.start() + # Starting DLT connector service grpc_service = DltConnectorService() grpc_service.start() @@ -56,6 +62,7 @@ def main(): while not terminate.wait(timeout=0.1): pass LOGGER.info('Terminating...') + dlt_events_collector.stop() grpc_service.stop() LOGGER.info('Bye') -- GitLab From 430382ca6b231d11910932f2fccb6444df0d4f15 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Fri, 28 Oct 2022 19:33:16 +0000 Subject: [PATCH 11/55] Interdomain component: - removed unneeded code in Dockerfile - created first partial version of TopologyAbstractor --- src/interdomain/Dockerfile | 7 +- src/interdomain/service/__main__.py | 12 +- .../topology_abstractor/TopologyAbstractor.py | 111 ++++++++++++++++++ .../service/topology_abstractor/__init__.py | 14 +++ .../tools/ContextMethods.py | 71 +++++++++++ .../topology_abstractor/tools/__init__.py | 14 +++ 6 files changed, 224 insertions(+), 5 deletions(-) create mode 100644 src/interdomain/service/topology_abstractor/TopologyAbstractor.py create mode 100644 src/interdomain/service/topology_abstractor/__init__.py create mode 100644 src/interdomain/service/topology_abstractor/tools/ContextMethods.py create mode 100644 src/interdomain/service/topology_abstractor/tools/__init__.py diff --git a/src/interdomain/Dockerfile b/src/interdomain/Dockerfile index 388fcb76d..036890dc4 100644 --- a/src/interdomain/Dockerfile +++ b/src/interdomain/Dockerfile @@ -63,10 +63,11 @@ RUN python3 -m pip install -r requirements.txt # Add component files into working directory WORKDIR /var/teraflow COPY src/context/. context/ -COPY src/device/. device/ +#COPY src/device/. device/ +COPY src/dlt/. dlt/ COPY src/interdomain/. interdomain/ -COPY src/monitoring/. monitoring/ -COPY src/service/. service/ +#COPY src/monitoring/. monitoring/ +#COPY src/service/. service/ COPY src/slice/. slice/ # Start the service diff --git a/src/interdomain/service/__main__.py b/src/interdomain/service/__main__.py index c0a078f4d..b5463d7d8 100644 --- a/src/interdomain/service/__main__.py +++ b/src/interdomain/service/__main__.py @@ -18,6 +18,7 @@ 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, get_service_port_grpc, wait_for_environment_variables) +from .topology_abstractor.TopologyAbstractor import TopologyAbstractor from .InterdomainService import InterdomainService from .RemoteDomainClients import RemoteDomainClients @@ -40,6 +41,8 @@ def main(): get_env_var_name(ServiceNameEnum.CONTEXT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), get_env_var_name(ServiceNameEnum.SLICE, ENVVAR_SUFIX_SERVICE_HOST ), get_env_var_name(ServiceNameEnum.SLICE, ENVVAR_SUFIX_SERVICE_PORT_GRPC), + get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_HOST ), + get_env_var_name(ServiceNameEnum.DLT, ENVVAR_SUFIX_SERVICE_PORT_GRPC), ]) signal.signal(signal.SIGINT, signal_handler) @@ -58,14 +61,19 @@ def main(): grpc_service = InterdomainService(remote_domain_clients) grpc_service.start() + # Subscribe to Context Events + topology_abstractor = TopologyAbstractor() + topology_abstractor.start() + # TODO: improve with configuration the definition of the remote peers - interdomain_service_port_grpc = get_service_port_grpc(ServiceNameEnum.INTERDOMAIN) - remote_domain_clients.add_peer('remote-teraflow', 'remote-teraflow', interdomain_service_port_grpc) + #interdomain_service_port_grpc = get_service_port_grpc(ServiceNameEnum.INTERDOMAIN) + #remote_domain_clients.add_peer('remote-teraflow', 'remote-teraflow', interdomain_service_port_grpc) # Wait for Ctrl+C or termination signal while not terminate.wait(timeout=0.1): pass LOGGER.info('Terminating...') + topology_abstractor.stop() grpc_service.stop() LOGGER.info('Bye') diff --git a/src/interdomain/service/topology_abstractor/TopologyAbstractor.py b/src/interdomain/service/topology_abstractor/TopologyAbstractor.py new file mode 100644 index 000000000..6773ded4a --- /dev/null +++ b/src/interdomain/service/topology_abstractor/TopologyAbstractor.py @@ -0,0 +1,111 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import threading +import logging, threading +from typing import List, Optional, Union +from common.Constants import AGGREGATED_TOPOLOGY_UUID, DOMAINS_TOPOLOGY_UUID +from common.proto.context_pb2 import ( + ConnectionEvent, ContextEvent, ContextId, DeviceEvent, DeviceId, LinkEvent, ServiceEvent, ServiceId, + SliceEvent, SliceId, TopologyEvent) +from context.client.ContextClient import ContextClient +from context.client.EventsCollector import EventsCollector +from dlt.connector.client.DltConnectorClient import DltConnectorClient +from .tools.ContextMethods import create_abstracted_device_if_not_exists, create_interdomain_entities + +LOGGER = logging.getLogger(__name__) + +DltRecordIdTypes = Union[DeviceId, SliceId, ServiceId] +EventTypes = Union[ + ContextEvent, TopologyEvent, DeviceEvent, LinkEvent, ServiceEvent, SliceEvent, ConnectionEvent +] + +class TopologyAbstractor(threading.Thread): + def __init__(self) -> None: + super().__init__(daemon=True) + self.terminate = threading.Event() + + self.context_client = ContextClient() + self.dlt_connector_client = DltConnectorClient() + self.context_event_collector = EventsCollector(self.context_client) + + self.own_context_id : Optional[ContextId] = None + self.own_abstract_device : Optional[ContextId] = None + + def stop(self): + self.terminate.set() + + def run(self) -> None: + self.context_client.connect() + self.dlt_connector_client.connect() + self.context_event_collector.start() + + while not self.terminate.is_set(): + event = self.context_event_collector.get_event(timeout=0.1) + if event is None: continue + if self.ignore_event(event): continue + # TODO: filter events resulting from abstraction computation + # TODO: filter events resulting from updating remote abstractions + LOGGER.info('Processing Event({:s})...'.format(str(event))) + dlt_records = self.update_abstraction(event) + self.send_dlt_records(dlt_records) + + self.context_event_collector.stop() + self.context_client.close() + self.dlt_connector_client.close() + + def ignore_event(self, event : EventTypes) -> List[DltRecordIdTypes]: + if isinstance(event, ContextEvent): + context_uuid = event.context_id.context_uuid.uuid + if self.own_context_id is None: return False + own_context_uuid = self.own_context_id.context_uuid.uuid + return context_uuid == own_context_uuid + elif isinstance(event, TopologyEvent): + context_uuid = event.topology_id.context_id.context_uuid.uuid + if self.own_context_id is None: return False + own_context_uuid = self.own_context_id.context_uuid.uuid + if context_uuid != own_context_uuid: return True + topology_uuid = event.topology_id.topology_uuid.uuid + if topology_uuid in {DOMAINS_TOPOLOGY_UUID, AGGREGATED_TOPOLOGY_UUID}: return True + return False + + def send_dlt_records(self, dlt_records : Union[DltRecordIdTypes, List[DltRecordIdTypes]]) -> None: + for dlt_record_id in dlt_records: + if isinstance(dlt_record_id, DeviceId): + self.dlt_connector_client.RecordDevice(dlt_record_id) + elif isinstance(dlt_record_id, ServiceId): + self.dlt_connector_client.RecordService(dlt_record_id) + elif isinstance(dlt_record_id, SliceId): + self.dlt_connector_client.RecordSlice(dlt_record_id) + else: + LOGGER.error('Unsupported Record({:s})'.format(str(dlt_record_id))) + + def update_abstraction(self, event : Optional[EventTypes] = None) -> List[DltRecordIdTypes]: + dlt_record_ids_with_changes = [] + + if self.own_context_id is None: + self.own_context_id = create_interdomain_entities(self.context_client) + + if self.own_abstract_device is None: + self.own_abstract_device = create_abstracted_device_if_not_exists(self.context_client, self.own_context_id) + dlt_record_ids_with_changes.append(self.own_abstract_device.device_id) + + if event is None: + # TODO: identify initial status from topology and update endpoints accordingly + pass + else: + # TODO: identify changes from event and update endpoints accordingly + pass + + return dlt_record_ids_with_changes diff --git a/src/interdomain/service/topology_abstractor/__init__.py b/src/interdomain/service/topology_abstractor/__init__.py new file mode 100644 index 000000000..70a332512 --- /dev/null +++ b/src/interdomain/service/topology_abstractor/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# 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/interdomain/service/topology_abstractor/tools/ContextMethods.py b/src/interdomain/service/topology_abstractor/tools/ContextMethods.py new file mode 100644 index 000000000..7b8e2eada --- /dev/null +++ b/src/interdomain/service/topology_abstractor/tools/ContextMethods.py @@ -0,0 +1,71 @@ +import copy +from common.Constants import ( + AGGREGATED_TOPOLOGY_UUID, DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID, DOMAINS_TOPOLOGY_UUID) +from common.DeviceTypes import DeviceTypeEnum +from common.proto.context_pb2 import ( + Context, ContextId, Device, DeviceDriverEnum, DeviceId, DeviceOperationalStatusEnum, Empty, Topology, TopologyId) +from common.tools.object_factory.Context import json_context, json_context_id +from common.tools.object_factory.Device import json_device, json_device_id +from common.tools.object_factory.Topology import json_topology, json_topology_id +from context.client.ContextClient import ContextClient + +def create_interdomain_entities(context_client : ContextClient) -> ContextId: + existing_context_ids = context_client.ListContextIds(Empty()) + existing_context_uuids = {context_id.context_uuid.uuid for context_id in existing_context_ids.context_ids} + + # Detect local context name (will be used as abstracted device name); exclude DEFAULT_CONTEXT_UUID + existing_non_admin_context_uuids = copy.deepcopy(existing_context_uuids) + existing_non_admin_context_uuids.discard(DEFAULT_CONTEXT_UUID) + if len(existing_non_admin_context_uuids) != 1: + MSG = 'Unable to identify own domain name. Existing Contexts({:s})' + raise Exception(MSG.format(str(existing_context_uuids))) + own_domain_uuid = existing_non_admin_context_uuids.pop() + own_context_id = ContextId(**json_context_id(own_domain_uuid)) + + #if DEFAULT_CONTEXT_UUID not in existing_context_uuids: + # context_client.SetContext(Context(**json_context(DEFAULT_CONTEXT_UUID))) + #admin_context_id = ContextId(**json_context_id(DEFAULT_CONTEXT_UUID)) + + # Create topologies "admin", "domains", and "aggregated" + existing_topology_ids = context_client.ListTopologyIds(own_context_id) + existing_topology_uuids = {topology_id.topology_uuid.uuid for topology_id in existing_topology_ids.topology_ids} + + topology_uuids = [DEFAULT_TOPOLOGY_UUID, DOMAINS_TOPOLOGY_UUID, AGGREGATED_TOPOLOGY_UUID] + for topology_uuid in topology_uuids: + if topology_uuid in existing_topology_uuids: continue + context_client.SetTopology(Topology(**json_topology(topology_uuid, context_id=own_context_id))) + + return own_context_id + +def create_abstracted_device_if_not_exists(context_client : ContextClient, own_context_id : ContextId) -> Device: + own_domain_uuid = own_context_id.context_uuid.uuid + + # Create device representing abstracted local domain + existing_device_ids = context_client.ListDeviceIds(Empty()) + existing_device_uuids = {device_id.device_uuid.uuid for device_id in existing_device_ids.device_ids} + own_abstract_device_id = DeviceId(**json_device_id(own_domain_uuid)) + if own_domain_uuid in existing_device_uuids: + own_abstract_device = context_client.GetDevice(own_abstract_device_id) + else: + own_abstracted_device_uuid = own_domain_uuid + own_abstract_device = Device(**json_device( + own_abstracted_device_uuid, DeviceTypeEnum.NETWORK.value, + DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED, + endpoints=[], config_rules=[], drivers=[DeviceDriverEnum.DEVICEDRIVER_UNDEFINED] + )) + context_client.SetDevice(own_abstract_device) + + # Add own abstracted device to topologies ["domains"] + topology_uuids = [DOMAINS_TOPOLOGY_UUID] + for topology_uuid in topology_uuids: + topology_id = TopologyId(**json_topology_id(topology_uuid, own_context_id)) + topology_ro = context_client.GetTopology(topology_id) + device_uuids = {device_id.device_uuid.uuid for device_id in topology_ro.device_ids} + if own_abstracted_device_uuid in device_uuids: continue + + topology_rw = Topology() + topology_rw.CopyFrom(topology_ro) + topology_rw.device_ids.add().device_uuid.uuid = own_abstracted_device_uuid + context_client.SetTopology(topology_rw) + + return own_abstract_device diff --git a/src/interdomain/service/topology_abstractor/tools/__init__.py b/src/interdomain/service/topology_abstractor/tools/__init__.py new file mode 100644 index 000000000..70a332512 --- /dev/null +++ b/src/interdomain/service/topology_abstractor/tools/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + -- GitLab From 4bc896fad2d66dfba7cb90ce66c068184d2de571 Mon Sep 17 00:00:00 2001 From: gifrerenom Date: Fri, 28 Oct 2022 19:35:50 +0000 Subject: [PATCH 12/55] WebUI component: - extneded context selection to context/topology - enabled selection of context/topology to plot - enabled listing of devices/links based on context/topology - updated network icon --- src/webui/service/__init__.py | 9 ++- src/webui/service/device/routes.py | 28 +++++-- src/webui/service/link/routes.py | 28 +++++-- src/webui/service/main/forms.py | 25 +++--- src/webui/service/main/routes.py | 73 +++++++++++++----- .../topology_icons/Acknowledgements.txt | 3 +- .../service/static/topology_icons/network.png | Bin 8988 -> 7520 bytes src/webui/service/templates/base.html | 2 +- src/webui/service/templates/main/home.html | 22 +++--- 9 files changed, 126 insertions(+), 64 deletions(-) diff --git a/src/webui/service/__init__.py b/src/webui/service/__init__.py index 75e103642..d60cca659 100644 --- a/src/webui/service/__init__.py +++ b/src/webui/service/__init__.py @@ -19,10 +19,10 @@ from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient def get_working_context() -> str: - if 'context_uuid' in session: - return session['context_uuid'] - else: - return 'Not selected' + return session['context_uuid'] if 'context_uuid' in session else '---' + +def get_working_topology() -> str: + return session['topology_uuid'] if 'topology_uuid' in session else '---' def liveness(): pass @@ -85,6 +85,7 @@ def create_app(use_config=None, web_app_root=None): app.jinja_env.filters['from_json'] = from_json app.jinja_env.globals.update(get_working_context=get_working_context) + app.jinja_env.globals.update(get_working_topology=get_working_topology) if web_app_root is not None: app.wsgi_app = SetSubAppMiddleware(app.wsgi_app, web_app_root) diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py index f1423e92e..b57c5735d 100644 --- a/src/webui/service/device/routes.py +++ b/src/webui/service/device/routes.py @@ -16,7 +16,9 @@ from flask import current_app, render_template, Blueprint, flash, session, redir from common.proto.context_pb2 import ( ConfigActionEnum, ConfigRule, Device, DeviceDriverEnum, DeviceId, DeviceList, DeviceOperationalStatusEnum, - Empty) + Empty, TopologyId) +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 device.client.DeviceClient import DeviceClient from webui.service.device.forms import AddDeviceForm @@ -27,16 +29,28 @@ device_client = DeviceClient() @device.get('/') def home(): - context_uuid = session.get('context_uuid', '-') - if context_uuid == "-": + if 'context_topology_uuid' not in session: flash("Please select a context!", "warning") return redirect(url_for("main.home")) + + context_uuid = session['context_uuid'] + topology_uuid = session['topology_uuid'] + context_client.connect() - response: DeviceList = context_client.ListDevices(Empty()) + json_topo_id = json_topology_id(topology_uuid, context_id=json_context_id(context_uuid)) + grpc_topology = context_client.GetTopology(TopologyId(**json_topo_id)) + topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids} + grpc_devices: DeviceList = context_client.ListDevices(Empty()) context_client.close() - return render_template('device/home.html', devices=response.devices, - dde=DeviceDriverEnum, - dose=DeviceOperationalStatusEnum) + + devices = [ + device for device in grpc_devices.devices + if device.device_id.device_uuid.uuid in topo_device_uuids + ] + + return render_template( + 'device/home.html', devices=devices, dde=DeviceDriverEnum, + dose=DeviceOperationalStatusEnum) @device.route('add', methods=['GET', 'POST']) def add(): diff --git a/src/webui/service/link/routes.py b/src/webui/service/link/routes.py index 51e903d9e..5b8831b77 100644 --- a/src/webui/service/link/routes.py +++ b/src/webui/service/link/routes.py @@ -14,7 +14,9 @@ from flask import current_app, render_template, Blueprint, flash, session, redirect, url_for -from common.proto.context_pb2 import Empty, Link, LinkEvent, LinkId, LinkIdList, LinkList, DeviceId +from common.proto.context_pb2 import Empty, Link, LinkEvent, LinkId, LinkIdList, LinkList, DeviceId, TopologyId +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 @@ -23,18 +25,28 @@ context_client = ContextClient() @link.get('/') def home(): - context_uuid = session.get('context_uuid', '-') - if context_uuid == "-": + if 'context_topology_uuid' not in session: flash("Please select a context!", "warning") return redirect(url_for("main.home")) - request = Empty() + + context_uuid = session['context_uuid'] + topology_uuid = session['topology_uuid'] + context_client.connect() - response = context_client.ListLinks(request) + json_topo_id = json_topology_id(topology_uuid, context_id=json_context_id(context_uuid)) + grpc_topology = context_client.GetTopology(TopologyId(**json_topo_id)) + topo_link_uuids = {link_id.link_uuid.uuid for link_id in grpc_topology.link_ids} + grpc_links: LinkList = context_client.ListLinks(Empty()) context_client.close() + + links = [ + link for link in grpc_links.links + if link.link_id.link_uuid.uuid in topo_link_uuids + ] + return render_template( - "link/home.html", - links=response.links, - ) + 'link/home.html', links=links) + @link.route('detail/', methods=('GET', 'POST')) def detail(link_uuid: str): diff --git a/src/webui/service/main/forms.py b/src/webui/service/main/forms.py index abef11e06..b138592fc 100644 --- a/src/webui/service/main/forms.py +++ b/src/webui/service/main/forms.py @@ -19,20 +19,21 @@ from wtforms import SelectField, FileField, SubmitField from wtforms.validators import DataRequired, Length -class ContextForm(FlaskForm): - context = SelectField( 'Context', - choices=[], - validators=[ - DataRequired(), - Length(min=1) - ]) - +class ContextTopologyForm(FlaskForm): + context_topology = SelectField( + 'Ctx/Topo', + choices=[], + validators=[ + DataRequired(), + Length(min=1) + ]) submit = SubmitField('Submit') class DescriptorForm(FlaskForm): - descriptors = FileField('Descriptors', - validators=[ - FileAllowed(['json'], 'JSON Descriptors only!') - ]) + descriptors = FileField( + 'Descriptors', + validators=[ + FileAllowed(['json'], 'JSON Descriptors only!') + ]) submit = SubmitField('Submit') diff --git a/src/webui/service/main/routes.py b/src/webui/service/main/routes.py index 9b1b08857..1ca5329e3 100644 --- a/src/webui/service/main/routes.py +++ b/src/webui/service/main/routes.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json, logging +import json, logging, re from flask import jsonify, redirect, render_template, Blueprint, flash, session, url_for, request -from common.proto.context_pb2 import Connection, Context, Device, Empty, Link, Service, Slice, Topology, ContextIdList +from common.proto.context_pb2 import Connection, Context, Device, Empty, Link, Service, Slice, Topology, ContextIdList, TopologyId, TopologyIdList from common.tools.grpc.Tools import 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 device.client.DeviceClient import DeviceClient from service.client.ServiceClient import ServiceClient @@ -23,7 +25,7 @@ from slice.client.SliceClient import SliceClient from webui.service.main.DescriptorTools import ( format_custom_config_rules, get_descriptors_add_contexts, get_descriptors_add_services, get_descriptors_add_slices, get_descriptors_add_topologies, split_devices_by_rules) -from webui.service.main.forms import ContextForm, DescriptorForm +from webui.service.main.forms import ContextTopologyForm, DescriptorForm main = Blueprint('main', __name__) @@ -154,20 +156,34 @@ def process_descriptors(descriptors): def home(): context_client.connect() device_client.connect() - response: ContextIdList = context_client.ListContextIds(Empty()) - context_form: ContextForm = ContextForm() - context_form.context.choices.append(('', 'Select...')) + context_topology_form: ContextTopologyForm = ContextTopologyForm() + context_topology_form.context_topology.choices.append(('', 'Select...')) - for context in response.context_ids: - context_form.context.choices.append((context.context_uuid.uuid, context.context_uuid)) + ctx_response: ContextIdList = context_client.ListContextIds(Empty()) + for context_id in ctx_response.context_ids: + context_uuid = context_id.context_uuid.uuid + topo_response: TopologyIdList = context_client.ListTopologyIds(context_id) + for topology_id in topo_response.topology_ids: + topology_uuid = topology_id.topology_uuid.uuid + context_topology_uuid = 'ctx[{:s}]/topo[{:s}]'.format(context_uuid, topology_uuid) + context_topology_name = 'Context({:s}):Topology({:s})'.format(context_uuid, topology_uuid) + context_topology_entry = (context_topology_uuid, context_topology_name) + context_topology_form.context_topology.choices.append(context_topology_entry) - if context_form.validate_on_submit(): - session['context_uuid'] = context_form.context.data - flash(f'The context was successfully set to `{context_form.context.data}`.', 'success') - return redirect(url_for("main.home")) + if context_topology_form.validate_on_submit(): + context_topology_uuid = context_topology_form.context_topology.data + if len(context_topology_uuid) > 0: + match = re.match('ctx\[([^\]]+)\]\/topo\[([^\]]+)\]', context_topology_uuid) + if match is not None: + session['context_topology_uuid'] = context_topology_uuid = match.group(0) + session['context_uuid'] = context_uuid = match.group(1) + session['topology_uuid'] = topology_uuid = match.group(2) + MSG = f'Context({context_uuid})/Topology({topology_uuid}) successfully selected.' + flash(MSG, 'success') + return redirect(url_for("main.home")) - if 'context_uuid' in session: - context_form.context.data = session['context_uuid'] + if 'context_topology_uuid' in session: + context_topology_form.context_topology.data = session['context_topology_uuid'] descriptor_form: DescriptorForm = DescriptorForm() try: @@ -181,22 +197,39 @@ def home(): context_client.close() device_client.close() - return render_template('main/home.html', context_form=context_form, descriptor_form=descriptor_form) + return render_template( + 'main/home.html', context_topology_form=context_topology_form, descriptor_form=descriptor_form) @main.route('/topology', methods=['GET']) def topology(): context_client.connect() try: + if 'context_topology_uuid' not in session: + return jsonify({'devices': [], 'links': []}) + + context_uuid = session['context_uuid'] + topology_uuid = session['topology_uuid'] + + json_topo_id = json_topology_id(topology_uuid, context_id=json_context_id(context_uuid)) + grpc_topology = context_client.GetTopology(TopologyId(**json_topo_id)) + + topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids} + topo_link_uuids = {link_id .link_uuid .uuid for link_id in grpc_topology.link_ids } + response = context_client.ListDevices(Empty()) - devices = [{ - 'id': device.device_id.device_uuid.uuid, - 'name': device.device_id.device_uuid.uuid, - 'type': device.device_type, - } for device in response.devices] + devices = [] + for device in response.devices: + if device.device_id.device_uuid.uuid not in topo_device_uuids: continue + devices.append({ + 'id': device.device_id.device_uuid.uuid, + 'name': device.device_id.device_uuid.uuid, + 'type': device.device_type, + }) response = context_client.ListLinks(Empty()) links = [] for link in response.links: + if link.link_id.link_uuid.uuid not in topo_link_uuids: continue if len(link.link_endpoint_ids) != 2: str_link = grpc_message_to_json_string(link) logger.warning('Unexpected link with len(endpoints) != 2: {:s}'.format(str_link)) diff --git a/src/webui/service/static/topology_icons/Acknowledgements.txt b/src/webui/service/static/topology_icons/Acknowledgements.txt index ddf7a8d0d..932103acc 100644 --- a/src/webui/service/static/topology_icons/Acknowledgements.txt +++ b/src/webui/service/static/topology_icons/Acknowledgements.txt @@ -1,6 +1,7 @@ Network Topology Icons taken from https://vecta.io/symbols -https://symbols.getvecta.com/stencil_240/51_cloud.4d0a827676.png => cloud.png +https://symbols.getvecta.com/stencil_240/51_cloud.4d0a827676.png => network.png + #modified to be grey instead of white https://symbols.getvecta.com/stencil_240/15_atm-switch.1bbf9a7cca.png => packet-switch.png https://symbols.getvecta.com/stencil_241/45_atm-switch.6a7362c1df.png => emu-packet-switch.png diff --git a/src/webui/service/static/topology_icons/network.png b/src/webui/service/static/topology_icons/network.png index 0f8e9c9714edd1c11904367ef1e9c60ef7ed3295..1f770f7bb2a31834a191e6c8727f059e1f14bbe1 100644 GIT binary patch literal 7520 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 z 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 O0V&C;%T`KbasLBlgSHX? diff --git a/src/webui/service/templates/base.html b/src/webui/service/templates/base.html index 5d7801d11..bee98ee82 100644 --- a/src/webui/service/templates/base.html +++ b/src/webui/service/templates/base.html @@ -103,7 +103,7 @@ - Current context: {{ get_working_context() }} + Current Context({{ get_working_context() }})/Topology({{ get_working_topology() }}) diff --git a/src/webui/service/templates/main/home.html b/src/webui/service/templates/main/home.html index db390939f..43b066cc0 100644 --- a/src/webui/service/templates/main/home.html +++ b/src/webui/service/templates/main/home.html @@ -19,7 +19,7 @@ {% block content %}

ETSI TeraFlowSDN Controller

- {% for field, message in context_form.errors.items() %} + {% for field, message in context_topology_form.errors.items() %}