Feature 10991: Gather information relating to the KNF being deployed and inject it... 56/14256/33
authorPedro Pereira <pedrocjdpereira@av.it.pt>
Tue, 12 Mar 2024 14:58:13 +0000 (14:58 +0000)
committerPedro Pereira <pedrocjdpereira@av.it.pt>
Mon, 1 Jul 2024 09:41:43 +0000 (10:41 +0100)
Change-Id: I5d78de8f369bd893a4a05287763909d2fe360b06
Signed-off-by: Pedro Pereira <pedrocjdpereira@av.it.pt>
n2vc/k8s_helm3_conn.py
n2vc/k8s_helm_base_conn.py
n2vc/post-renderer-scripts/mainPostRenderer/mainPostRenderer [new file with mode: 0755]
n2vc/post-renderer-scripts/podLabels/kustomization.yaml [new file with mode: 0644]
n2vc/post-renderer-scripts/podLabels/podLabels [new file with mode: 0755]
n2vc/tests/unit/test_k8s_helm3_conn.py

index b267c75..ed0bdaa 100644 (file)
@@ -107,6 +107,10 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
 
         self.log.debug("installing {} in cluster {}".format(kdu_model, cluster_uuid))
 
+        labels_dict = None
+        if db_dict:
+            labels_dict = await self._labels_dict(db_dict, kdu_instance)
+
         # sync local dir
         self.fs.sync(from_path=cluster_uuid)
 
@@ -140,6 +144,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
             timeout=timeout,
             params=params,
             db_dict=db_dict,
+            labels=labels_dict,
             kdu_name=kdu_name,
             namespace=namespace,
         )
@@ -448,6 +453,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
         kdu_model: str,
         kdu_instance: str,
         namespace: str,
+        labels: dict,
         params_str: str,
         version: str,
         atomic: bool,
@@ -472,9 +478,30 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
         if version:
             version_str = "--version {}".format(version)
 
+        # labels
+        post_renderer_args = []
+        post_renderer_str = post_renderer_args_str = ""
+        if labels and self.podLabels_post_renderer_path:
+            post_renderer_args.append(
+                "{}={}".format(
+                    self.podLabels_post_renderer_path,
+                    " ".join(
+                        ["{}:{}".format(key, value) for key, value in labels.items()]
+                    ),
+                )
+            )
+
+        if len(post_renderer_args) > 0 and self.main_post_renderer_path:
+            post_renderer_str = "--post-renderer {}".format(
+                self.main_post_renderer_path,
+            )
+            post_renderer_args_str += (
+                "--post-renderer-args '" + ",".join(post_renderer_args) + "'"
+            )
+
         command = (
             "env KUBECONFIG={kubeconfig} {helm} install {name} {atomic} --output yaml  "
-            "{params} {timeout} {ns} {model} {ver}".format(
+            "{params} {timeout} {ns} {post_renderer} {post_renderer_args} {model} {ver}".format(
                 kubeconfig=kubeconfig,
                 helm=self._helm_command,
                 name=quote(kdu_instance),
@@ -482,6 +509,8 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
                 params=params_str,
                 timeout=timeout_str,
                 ns=namespace_str,
+                post_renderer=post_renderer_str,
+                post_renderer_args=post_renderer_args_str,
                 model=quote(kdu_model),
                 ver=version_str,
             )
@@ -494,6 +523,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
         kdu_instance: str,
         namespace: str,
         scale: int,
+        labels: dict,
         version: str,
         atomic: bool,
         replica_str: str,
@@ -533,6 +563,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
             kdu_instance=kdu_instance,
             namespace=namespace,
             params_str=scale_str,
+            labels=labels,
             version=version,
             atomic=atomic,
             timeout=timeout,
@@ -545,6 +576,7 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
         kdu_instance: str,
         namespace: str,
         params_str: str,
+        labels: dict,
         version: str,
         atomic: bool,
         timeout: float,
@@ -606,9 +638,31 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
             on_values_str = "--reuse-values"
         elif reset_then_reuse_values:
             on_values_str = "--reset-then-reuse-values"
+
+        # labels
+        post_renderer_args = []
+        post_renderer_str = post_renderer_args_str = ""
+        if labels and self.podLabels_post_renderer_path:
+            post_renderer_args.append(
+                "{}={}".format(
+                    self.podLabels_post_renderer_path,
+                    " ".join(
+                        ["{}:{}".format(key, value) for key, value in labels.items()]
+                    ),
+                )
+            )
+
+        if len(post_renderer_args) > 0 and self.main_post_renderer_path:
+            post_renderer_str = "--post-renderer {}".format(
+                self.main_post_renderer_path,
+            )
+            post_renderer_args_str += (
+                "--post-renderer-args '" + ",".join(post_renderer_args) + "'"
+            )
+
         command = (
             "env KUBECONFIG={kubeconfig} {helm} upgrade {name} {model} {namespace} {atomic} {force}"
-            "--output yaml {params} {timeout} {on_values} {ver}"
+            "--output yaml {params} {timeout} {post_renderer} {post_renderer_args} {on_values} {ver}"
         ).format(
             kubeconfig=kubeconfig,
             helm=self._helm_command,
@@ -618,6 +672,8 @@ class K8sHelm3Connector(K8sHelmBaseConnector):
             force=force_str,
             params=params_str,
             timeout=timeout_str,
+            post_renderer=post_renderer_str,
+            post_renderer_args=post_renderer_args_str,
             model=quote(kdu_model),
             on_values=on_values_str,
             ver=version_str,
index abf2d7e..8c364be 100644 (file)
@@ -88,6 +88,24 @@ class K8sHelmBaseConnector(K8sConnector):
         self._helm_command = helm_command
         self._check_file_exists(filename=helm_command, exception_if_not_exists=True)
 
+        # exception if main post renderer executable is not present
+        self.main_post_renderer_path = EnvironConfig(prefixes=["OSMLCM_"]).get(
+            "mainpostrendererpath"
+        )
+        if self.main_post_renderer_path:
+            self._check_file_exists(
+                filename=self.main_post_renderer_path, exception_if_not_exists=True
+            )
+
+        # exception if podLabels post renderer executable is not present
+        self.podLabels_post_renderer_path = EnvironConfig(prefixes=["OSMLCM_"]).get(
+            "podlabelspostrendererpath"
+        )
+        if self.podLabels_post_renderer_path:
+            self._check_file_exists(
+                filename=self.podLabels_post_renderer_path, exception_if_not_exists=True
+            )
+
         # obtain stable repo url from config or apply default
         self._stable_repo_url = self.config.get("stablerepourl")
         if self._stable_repo_url == "None":
@@ -411,6 +429,7 @@ class K8sHelmBaseConnector(K8sConnector):
         timeout: float = 300,
         params: dict = None,
         db_dict: dict = None,
+        labels: dict = None,
         kdu_name: str = None,
         namespace: str = None,
     ):
@@ -430,6 +449,7 @@ class K8sHelmBaseConnector(K8sConnector):
             kdu_model,
             kdu_instance,
             namespace,
+            labels,
             params_str,
             version,
             atomic,
@@ -533,11 +553,18 @@ class K8sHelmBaseConnector(K8sConnector):
 
         kdu_model, version = await self._prepare_helm_chart(kdu_model, cluster_uuid)
 
+        labels_dict = None
+        if db_dict and await self._contains_labels(
+            kdu_instance, namespace, paths["kube_config"], env
+        ):
+            labels_dict = await self._labels_dict(db_dict, kdu_instance)
+
         command = self._get_upgrade_command(
             kdu_model,
             kdu_instance,
             namespace,
             params_str,
+            labels_dict,
             version,
             atomic,
             timeout,
@@ -670,11 +697,18 @@ class K8sHelmBaseConnector(K8sConnector):
             kdu_model, repo_url, resource_name
         )
 
+        labels_dict = None
+        if db_dict and await self._contains_labels(
+            kdu_instance, instance_info["namespace"], paths["kube_config"], env
+        ):
+            labels_dict = await self._labels_dict(db_dict, kdu_instance)
+
         command = self._get_upgrade_scale_command(
             kdu_model,
             kdu_instance,
             instance_info["namespace"],
             scale,
+            labels_dict,
             version,
             atomic,
             replica_str,
@@ -1316,6 +1350,7 @@ class K8sHelmBaseConnector(K8sConnector):
         kdu_model,
         kdu_instance,
         namespace,
+        labels,
         params_str,
         version,
         atomic,
@@ -1333,6 +1368,7 @@ class K8sHelmBaseConnector(K8sConnector):
         kdu_instance,
         namespace,
         count,
+        labels,
         version,
         atomic,
         replicas,
@@ -1366,6 +1402,7 @@ class K8sHelmBaseConnector(K8sConnector):
         kdu_instance,
         namespace,
         params_str,
+        labels,
         version,
         atomic,
         timeout,
@@ -1926,6 +1963,75 @@ class K8sHelmBaseConnector(K8sConnector):
 
         return replicas
 
+    async def _labels_dict(self, db_dict, kdu_instance):
+        # get the network service registry
+        ns_id = db_dict["filter"]["_id"]
+        try:
+            db_nsr = self.db.get_one("nsrs", {"_id": ns_id})
+        except Exception as e:
+            print("nsr {} not found: {}".format(ns_id, e))
+        nsd_id = db_nsr["nsd"]["_id"]
+
+        # get the kdu registry
+        for index, kdu in enumerate(db_nsr["_admin"]["deployed"]["K8s"]):
+            if kdu["kdu-instance"] == kdu_instance:
+                db_kdur = kdu
+                kdu_name = kdu["kdu-name"]
+                break
+        member_vnf_index = db_kdur["member-vnf-index"]
+        # get the vnf registry
+        try:
+            db_vnfr = self.db.get_one(
+                "vnfrs",
+                {"nsr-id-ref": ns_id, "member-vnf-index-ref": member_vnf_index},
+            )
+        except Exception as e:
+            print("vnfr {} not found: {}".format(member_vnf_index, e))
+
+        vnf_id = db_vnfr["_id"]
+        vnfd_id = db_vnfr["vnfd-id"]
+
+        return {
+            "managed-by": "osm.etsi.org",
+            "osm.etsi.org/ns-id": ns_id,
+            "osm.etsi.org/nsd-id": nsd_id,
+            "osm.etsi.org/vnf-id": vnf_id,
+            "osm.etsi.org/vnfd-id": vnfd_id,
+            "osm.etsi.org/kdu-id": kdu_instance,
+            "osm.etsi.org/kdu-name": kdu_name,
+        }
+
+    async def _contains_labels(self, kdu_instance, namespace, kube_config, env):
+        command = "env KUBECONFIG={} {} get manifest {} --namespace={}".format(
+            kube_config,
+            self._helm_command,
+            quote(kdu_instance),
+            quote(namespace),
+        )
+        output, rc = await self._local_async_exec(
+            command=command, raise_exception_on_error=False, env=env
+        )
+        manifests = yaml.safe_load_all(output)
+        for manifest in manifests:
+            # Check if the manifest has metadata and labels
+            if (
+                manifest is not None
+                and "metadata" in manifest
+                and "labels" in manifest["metadata"]
+            ):
+                labels = {
+                    "managed-by",
+                    "osm.etsi.org/kdu-id",
+                    "osm.etsi.org/kdu-name",
+                    "osm.etsi.org/ns-id",
+                    "osm.etsi.org/nsd-id",
+                    "osm.etsi.org/vnf-id",
+                    "osm.etsi.org/vnfd-id",
+                }
+                if labels.issubset(manifest["metadata"]["labels"].keys()):
+                    return True
+        return False
+
     async def _store_status(
         self,
         cluster_id: str,
diff --git a/n2vc/post-renderer-scripts/mainPostRenderer/mainPostRenderer b/n2vc/post-renderer-scripts/mainPostRenderer/mainPostRenderer
new file mode 100755 (executable)
index 0000000..740c62f
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/bash
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# 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.
+#######################################################################################
+# Default values for osm.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+IFS=',' read -r -a args <<< "$1"
+rendered_output=$(cat /dev/stdin)
+
+for arg in "${args[@]}"; do
+    key=$(echo "$arg" | cut -d'=' -f1)
+    value=$(echo "$arg" | cut -d'=' -f2)
+
+    rendered_output=$(echo "$rendered_output" | "$key" "$value")
+done
+
+echo "$rendered_output"
\ No newline at end of file
diff --git a/n2vc/post-renderer-scripts/podLabels/kustomization.yaml b/n2vc/post-renderer-scripts/podLabels/kustomization.yaml
new file mode 100644 (file)
index 0000000..358978b
--- /dev/null
@@ -0,0 +1,23 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# 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.
+#######################################################################################
+# Default values for osm.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+- all.yaml
\ No newline at end of file
diff --git a/n2vc/post-renderer-scripts/podLabels/podLabels b/n2vc/post-renderer-scripts/podLabels/podLabels
new file mode 100755 (executable)
index 0000000..0eb2a64
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/bash
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# 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.
+#######################################################################################
+# Default values for osm.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+dirname="podlabels-"$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13)
+
+mkdir /tmp/$dirname && cd /tmp/$dirname
+
+cp /app/N2VC/n2vc/post-renderer-scripts/podLabels/kustomization.yaml .
+
+cat <&0 > all.yaml
+
+IFS=' ' read -r -a labels <<< "$1"
+content="commonLabels:"
+for label in "${labels[@]}"; do
+    key=$(echo "$label" | cut -d':' -f1)
+    value=$(echo "$label" | cut -d':' -f2)
+    content="$content
+  $key: $value"
+done
+
+# Add content to the file
+echo -e "\n$content" >> kustomization.yaml
+
+kubectl kustomize && cd .. && rm -r /tmp/$dirname
\ No newline at end of file
index bddfddd..630962e 100644 (file)
@@ -217,7 +217,7 @@ class TestK8sHelm3Conn(asynctest.TestCase):
         command = (
             "env KUBECONFIG=./tmp/helm3_cluster_id/.kube/config /usr/bin/helm3 "
             "install stable-openldap-0005399828 --atomic --output yaml   "
-            "--timeout 300s --namespace testk8s stable/openldap --version 1.2.2"
+            "--timeout 300s --namespace testk8s   stable/openldap --version 1.2.2"
         )
         self.helm_conn._local_async_exec.assert_called_with(
             command=command, env=self.env, raise_exception_on_error=False
@@ -297,7 +297,7 @@ class TestK8sHelm3Conn(asynctest.TestCase):
         command = (
             "env KUBECONFIG=./tmp/helm3_cluster_id/.kube/config "
             "/usr/bin/helm3 upgrade stable-openldap-0005399828 stable/openldap "
-            "--namespace testk8s --atomic --force --output yaml  --timeout 300s "
+            "--namespace testk8s --atomic --force --output yaml  --timeout 300s   "
             "--reuse-values --version 1.2.3"
         )
         self.helm_conn._local_async_exec.assert_called_with(
@@ -329,7 +329,7 @@ class TestK8sHelm3Conn(asynctest.TestCase):
         command = (
             "env KUBECONFIG=./tmp/helm3_cluster_id/.kube/config "
             "/usr/bin/helm3 upgrade stable-openldap-0005399828 stable/openldap "
-            "--namespace testk8s --atomic --output yaml  --timeout 300s "
+            "--namespace testk8s --atomic --output yaml  --timeout 300s   "
             "--reuse-values --version 1.2.3"
         )
         self.helm_conn._local_async_exec.assert_called_with(
@@ -380,7 +380,7 @@ class TestK8sHelm3Conn(asynctest.TestCase):
         command = (
             "env KUBECONFIG=./tmp/helm3_cluster_id/.kube/config "
             "/usr/bin/helm3 upgrade stable-openldap-0005399828 stable/openldap "
-            "--namespace default --atomic --output yaml  --timeout 300s "
+            "--namespace default --atomic --output yaml  --timeout 300s   "
             "--reuse-values --version 1.2.3"
         )
         self.helm_conn._local_async_exec.assert_called_with(
@@ -437,7 +437,7 @@ class TestK8sHelm3Conn(asynctest.TestCase):
         command = (
             "env KUBECONFIG=./tmp/helm3_cluster_id/.kube/config "
             "/usr/bin/helm3 upgrade stable-openldap-0005399828 stable/openldap "
-            "--namespace testk8s --atomic --output yaml --set replicaCount=2 --timeout 1800s "
+            "--namespace testk8s --atomic --output yaml --set replicaCount=2 --timeout 1800s   "
             "--reuse-values --version 1.2.3"
         )
         self.helm_conn._local_async_exec.assert_called_with(
@@ -456,7 +456,7 @@ class TestK8sHelm3Conn(asynctest.TestCase):
         command = (
             "env KUBECONFIG=./tmp/helm3_cluster_id/.kube/config "
             "/usr/bin/helm3 upgrade stable-openldap-0005399828 stable/openldap "
-            "--namespace testk8s --atomic --output yaml --set dummy-app.replicas=3 --timeout 1800s "
+            "--namespace testk8s --atomic --output yaml --set dummy-app.replicas=3 --timeout 1800s   "
             "--reuse-values --version 1.2.3"
         )
         self.helm_conn._local_async_exec.assert_called_with(