From be693155db652697a505e574ce51cf76b569e79f Mon Sep 17 00:00:00 2001 From: Pedro Pereira Date: Tue, 12 Mar 2024 14:58:13 +0000 Subject: [PATCH] Feature 10991: Gather information relating to the KNF being deployed and inject it as labels in the Kubernetes objects Change-Id: I5d78de8f369bd893a4a05287763909d2fe360b06 Signed-off-by: Pedro Pereira --- n2vc/k8s_helm3_conn.py | 60 +++++++++- n2vc/k8s_helm_base_conn.py | 106 ++++++++++++++++++ .../mainPostRenderer/mainPostRenderer | 32 ++++++ .../podLabels/kustomization.yaml | 23 ++++ .../post-renderer-scripts/podLabels/podLabels | 42 +++++++ n2vc/tests/unit/test_k8s_helm3_conn.py | 12 +- 6 files changed, 267 insertions(+), 8 deletions(-) create mode 100755 n2vc/post-renderer-scripts/mainPostRenderer/mainPostRenderer create mode 100644 n2vc/post-renderer-scripts/podLabels/kustomization.yaml create mode 100755 n2vc/post-renderer-scripts/podLabels/podLabels diff --git a/n2vc/k8s_helm3_conn.py b/n2vc/k8s_helm3_conn.py index b267c75..ed0bdaa 100644 --- a/n2vc/k8s_helm3_conn.py +++ b/n2vc/k8s_helm3_conn.py @@ -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, diff --git a/n2vc/k8s_helm_base_conn.py b/n2vc/k8s_helm_base_conn.py index abf2d7e..8c364be 100644 --- a/n2vc/k8s_helm_base_conn.py +++ b/n2vc/k8s_helm_base_conn.py @@ -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 index 0000000..740c62f --- /dev/null +++ b/n2vc/post-renderer-scripts/mainPostRenderer/mainPostRenderer @@ -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 index 0000000..358978b --- /dev/null +++ b/n2vc/post-renderer-scripts/podLabels/kustomization.yaml @@ -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 index 0000000..0eb2a64 --- /dev/null +++ b/n2vc/post-renderer-scripts/podLabels/podLabels @@ -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 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 diff --git a/n2vc/tests/unit/test_k8s_helm3_conn.py b/n2vc/tests/unit/test_k8s_helm3_conn.py index bddfddd..630962e 100644 --- a/n2vc/tests/unit/test_k8s_helm3_conn.py +++ b/n2vc/tests/unit/test_k8s_helm3_conn.py @@ -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( -- 2.25.1