Add custom label to track created resources 35/91635/7
authorKiran Kamineni <kiran.k.kamineni@intel.com>
Wed, 17 Jul 2019 23:55:00 +0000 (16:55 -0700)
committerKiran Kamineni <kiran.k.kamineni@intel.com>
Wed, 7 Aug 2019 20:06:51 +0000 (13:06 -0700)
Create a custom label on created resources
Also, create it on pods where pods are being
created.
This will help us later for filtering and querying
pods and resources.

Issue-ID: MULTICLOUD-675
Change-Id: I4b4fce7b67f9f27559d99dcca94a9191b96cb7c6
Signed-off-by: Kiran Kamineni <kiran.k.kamineni@intel.com>
src/k8splugin/internal/app/client.go
src/k8splugin/internal/app/client_test.go
src/k8splugin/internal/app/config_backend.go
src/k8splugin/internal/app/instance.go
src/k8splugin/internal/config/config.go
src/k8splugin/internal/plugin/helpers.go
src/k8splugin/plugins/generic/plugin.go
src/k8splugin/plugins/namespace/plugin_test.go
src/k8splugin/plugins/service/plugin.go
src/k8splugin/plugins/service/plugin_test.go

index d44f350..914a8ee 100644 (file)
@@ -39,6 +39,7 @@ type KubernetesClient struct {
        dynamicClient  dynamic.Interface
        discoverClient discovery.CachedDiscoveryInterface
        restMapper     meta.RESTMapper
+       instanceID     string
 }
 
 // getKubeConfig uses the connectivity client to get the kubeconfig based on the name
@@ -55,11 +56,17 @@ func (k *KubernetesClient) getKubeConfig(cloudregion string) (string, error) {
 }
 
 // init loads the Kubernetes configuation values stored into the local configuration file
-func (k *KubernetesClient) init(cloudregion string) error {
+func (k *KubernetesClient) init(cloudregion string, iid string) error {
        if cloudregion == "" {
                return pkgerrors.New("Cloudregion is empty")
        }
 
+       if iid == "" {
+               return pkgerrors.New("Instance ID is empty")
+       }
+
+       k.instanceID = iid
+
        configPath, err := k.getKubeConfig(cloudregion)
        if err != nil {
                return pkgerrors.Wrap(err, "Get kubeconfig file")
@@ -89,6 +96,7 @@ func (k *KubernetesClient) init(cloudregion string) error {
        }
 
        k.restMapper = restmapper.NewDeferredDiscoveryRESTMapper(k.discoverClient)
+
        return nil
 }
 
@@ -211,3 +219,9 @@ func (k *KubernetesClient) GetDynamicClient() dynamic.Interface {
 func (k *KubernetesClient) GetStandardClient() kubernetes.Interface {
        return k.clientSet
 }
+
+//GetInstanceID returns the instanceID that is injected into all the
+//resources created by the plugin
+func (k *KubernetesClient) GetInstanceID() string {
+       return k.instanceID
+}
index fd293ab..7001d9e 100644 (file)
@@ -72,7 +72,7 @@ func TestInit(t *testing.T) {
 
                kubeClient := KubernetesClient{}
                // Refer to the connection via its name
-               err = kubeClient.init("mock_connection")
+               err = kubeClient.init("mock_connection", "abcdefg")
                if err != nil {
                        t.Fatalf("TestGetKubeClient returned an error (%s)", err)
                }
index b31cbac..6bc145e 100644 (file)
@@ -354,7 +354,7 @@ func scheduleResources(c chan configResourceList) {
                        log.Printf("[scheduleResources]: POST %v %v", data.profile, data.resourceTemplates)
                        for _, inst := range resp {
                                k8sClient := KubernetesClient{}
-                               err = k8sClient.init(inst.Request.CloudRegion)
+                               err = k8sClient.init(inst.Request.CloudRegion, inst.ID)
                                if err != nil {
                                        log.Printf("Getting CloudRegion Information: %s", err.Error())
                                        //Move onto the next cloud region
@@ -374,7 +374,7 @@ func scheduleResources(c chan configResourceList) {
                        log.Printf("[scheduleResources]: DELETE %v %v", data.profile, data.resourceTemplates)
                        for _, inst := range resp {
                                k8sClient := KubernetesClient{}
-                               err = k8sClient.init(inst.Request.CloudRegion)
+                               err = k8sClient.init(inst.Request.CloudRegion, inst.ID)
                                if err != nil {
                                        log.Printf("Getting CloudRegion Information: %s", err.Error())
                                        //Move onto the next cloud region
index d28fe79..5cfdaea 100644 (file)
@@ -127,8 +127,10 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) {
                return InstanceResponse{}, pkgerrors.Wrap(err, "Error resolving helm charts")
        }
 
+       id := generateInstanceID()
+
        k8sClient := KubernetesClient{}
-       err = k8sClient.init(i.CloudRegion)
+       err = k8sClient.init(i.CloudRegion, id)
        if err != nil {
                return InstanceResponse{}, pkgerrors.Wrap(err, "Getting CloudRegion Information")
        }
@@ -138,8 +140,6 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) {
                return InstanceResponse{}, pkgerrors.Wrap(err, "Create Kubernetes Resources")
        }
 
-       id := generateInstanceID()
-
        //Compose the return response
        resp := InstanceResponse{
                ID:        id,
@@ -292,7 +292,7 @@ func (v *InstanceClient) Delete(id string) error {
        }
 
        k8sClient := KubernetesClient{}
-       err = k8sClient.init(inst.Request.CloudRegion)
+       err = k8sClient.init(inst.Request.CloudRegion, inst.ID)
        if err != nil {
                return pkgerrors.Wrap(err, "Getting CloudRegion Information")
        }
index ac65328..23ec401 100644 (file)
@@ -26,19 +26,20 @@ import (
 // Configuration loads up all the values that are used to configure
 // backend implementations
 type Configuration struct {
-       CAFile            string `json:"ca-file"`
-       ServerCert        string `json:"server-cert"`
-       ServerKey         string `json:"server-key"`
-       Password          string `json:"password"`
-       DatabaseAddress   string `json:"database-address"`
-       DatabaseType      string `json:"database-type"`
-       PluginDir         string `json:"plugin-dir"`
-       EtcdIP            string `json:"etcd-ip"`
-       EtcdCert          string `json:"etcd-cert"`
-       EtcdKey           string `json:"etcd-key"`
-       EtcdCAFile        string `json:"etcd-ca-file"`
-       OVNCentralAddress string `json:"ovn-central-address"`
-       ServicePort       string `json:"service-port"`
+       CAFile              string `json:"ca-file"`
+       ServerCert          string `json:"server-cert"`
+       ServerKey           string `json:"server-key"`
+       Password            string `json:"password"`
+       DatabaseAddress     string `json:"database-address"`
+       DatabaseType        string `json:"database-type"`
+       PluginDir           string `json:"plugin-dir"`
+       EtcdIP              string `json:"etcd-ip"`
+       EtcdCert            string `json:"etcd-cert"`
+       EtcdKey             string `json:"etcd-key"`
+       EtcdCAFile          string `json:"etcd-ca-file"`
+       OVNCentralAddress   string `json:"ovn-central-address"`
+       ServicePort         string `json:"service-port"`
+       KubernetesLabelName string `json:"kubernetes-label-name"`
 }
 
 // Config is the structure that stores the configuration
@@ -74,19 +75,20 @@ func defaultConfiguration() *Configuration {
        }
 
        return &Configuration{
-               CAFile:            "ca.cert",
-               ServerCert:        "server.cert",
-               ServerKey:         "server.key",
-               Password:          "",
-               DatabaseAddress:   "127.0.0.1",
-               DatabaseType:      "mongo",
-               PluginDir:         cwd,
-               EtcdIP:            "127.0.0.1",
-               EtcdCert:          "etcd.cert",
-               EtcdKey:           "etcd.key",
-               EtcdCAFile:        "etcd-ca.cert",
-               OVNCentralAddress: "127.0.0.1:6641",
-               ServicePort:       "9015",
+               CAFile:              "ca.cert",
+               ServerCert:          "server.cert",
+               ServerKey:           "server.key",
+               Password:            "",
+               DatabaseAddress:     "127.0.0.1",
+               DatabaseType:        "mongo",
+               PluginDir:           cwd,
+               EtcdIP:              "127.0.0.1",
+               EtcdCert:            "etcd.cert",
+               EtcdKey:             "etcd.key",
+               EtcdCAFile:          "etcd-ca.cert",
+               OVNCentralAddress:   "127.0.0.1:6641",
+               ServicePort:         "9015",
+               KubernetesLabelName: "k8splugin.io/rb-instance-id",
        }
 }
 
index 26e0f46..b5c9109 100644 (file)
 package plugin
 
 import (
+       "encoding/json"
        "log"
        "strings"
 
        utils "github.com/onap/multicloud-k8s/src/k8splugin/internal"
+       "github.com/onap/multicloud-k8s/src/k8splugin/internal/config"
        "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
 
        pkgerrors "github.com/pkg/errors"
+       corev1 "k8s.io/api/core/v1"
        "k8s.io/apimachinery/pkg/api/meta"
+       "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
        "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/client-go/dynamic"
        "k8s.io/client-go/kubernetes"
@@ -45,6 +49,9 @@ type KubernetesConnector interface {
        // GetStandardClient returns the standard client that can be used to handle
        // standard kubernetes kinds
        GetStandardClient() kubernetes.Interface
+
+       //GetInstanceID returns the InstanceID for tracking during creation
+       GetInstanceID() string
 }
 
 // Reference is the interface that is implemented
@@ -90,3 +97,54 @@ func GetPluginByKind(kind string) (Reference, error) {
 
        return pluginImpl, nil
 }
+
+// TagPodsIfPresent finds the PodTemplateSpec from any workload
+// object that contains it and changes the spec to include the tag label
+func TagPodsIfPresent(unstruct *unstructured.Unstructured, tag string) {
+
+       spec, ok := unstruct.Object["spec"].(map[string]interface{})
+       if !ok {
+               log.Println("Error converting spec to map")
+               return
+       }
+       template, ok := spec["template"].(map[string]interface{})
+       if !ok {
+               log.Println("Error converting template to map")
+               return
+       }
+
+       data, err := json.Marshal(template)
+       if err != nil {
+               log.Println("Error Marshaling Podspec")
+               return
+       }
+
+       //Attempt to convert the template to a podtemplatespec.
+       //This is to check if we have any pods being created.
+       podTemplateSpec := &corev1.PodTemplateSpec{}
+       _, err = podTemplateSpec.MarshalTo(data)
+       if err != nil {
+               log.Println("Did not find a podTemplateSpec" + err.Error())
+               return
+       }
+
+       //At this point, we know that the data contains a PodTemplateSpec
+       metadata, ok := template["metadata"].(map[string]interface{})
+       if !ok {
+               log.Println("Error converting metadata to map")
+               return
+       }
+
+       //Get the labels map
+       labels, ok := metadata["labels"].(map[string]string)
+       if !ok {
+               log.Println("Error converting labels to map")
+               return
+       }
+
+       //Check if labels exist for this object
+       if labels == nil {
+               labels = map[string]string{}
+       }
+       labels[config.GetConfiguration().KubernetesLabelName] = tag
+}
index cc5fcb7..0711466 100644 (file)
@@ -21,6 +21,7 @@ import (
        "k8s.io/apimachinery/pkg/runtime/schema"
 
        utils "github.com/onap/multicloud-k8s/src/k8splugin/internal"
+       "github.com/onap/multicloud-k8s/src/k8splugin/internal/config"
        "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
        "github.com/onap/multicloud-k8s/src/k8splugin/internal/plugin"
 )
@@ -57,6 +58,20 @@ func (g genericPlugin) Create(yamlFilePath string, namespace string, client plug
                return "", pkgerrors.Wrap(err, "Mapping kind to resource error")
        }
 
+       //Add the tracking label to all resources created here
+       labels := unstruct.GetLabels()
+       //Check if labels exist for this object
+       if labels == nil {
+               labels = map[string]string{}
+       }
+       labels[config.GetConfiguration().KubernetesLabelName] = client.GetInstanceID()
+       unstruct.SetLabels(labels)
+
+       // This checks if the resource we are creating has a podSpec in it
+       // Eg: Deployment, StatefulSet, Job etc..
+       // If a PodSpec is found, the label will be added to it too.
+       plugin.TagPodsIfPresent(unstruct, client.GetInstanceID())
+
        gvr := mapping.Resource
        var createdObj *unstructured.Unstructured
 
index 489ac09..c1944a4 100644 (file)
@@ -46,6 +46,10 @@ func (t TestKubernetesConnector) GetStandardClient() kubernetes.Interface {
        return fake.NewSimpleClientset(t.object)
 }
 
+func (t TestKubernetesConnector) GetInstanceID() string {
+       return ""
+}
+
 func TestCreateNamespace(t *testing.T) {
        testCases := []struct {
                label          string
index 136a134..4c1f37b 100644 (file)
@@ -22,6 +22,7 @@ import (
        "k8s.io/apimachinery/pkg/runtime/schema"
 
        utils "github.com/onap/multicloud-k8s/src/k8splugin/internal"
+       "github.com/onap/multicloud-k8s/src/k8splugin/internal/config"
        "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
        "github.com/onap/multicloud-k8s/src/k8splugin/internal/plugin"
 )
@@ -52,6 +53,14 @@ func (p servicePlugin) Create(yamlFilePath string, namespace string, client plug
        }
        service.Namespace = namespace
 
+       labels := service.GetLabels()
+       //Check if labels exist for this object
+       if labels == nil {
+               labels = map[string]string{}
+       }
+       labels[config.GetConfiguration().KubernetesLabelName] = client.GetInstanceID()
+       service.SetLabels(labels)
+
        result, err := client.GetStandardClient().CoreV1().Services(namespace).Create(service)
        if err != nil {
                return "", pkgerrors.Wrap(err, "Create Service error")
index aa0bcc2..1cef502 100644 (file)
@@ -14,11 +14,12 @@ limitations under the License.
 package main
 
 import (
-       "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
        "reflect"
        "strings"
        "testing"
 
+       "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
+
        coreV1 "k8s.io/api/core/v1"
        "k8s.io/apimachinery/pkg/api/meta"
        metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -45,6 +46,10 @@ func (t TestKubernetesConnector) GetStandardClient() kubernetes.Interface {
        return fake.NewSimpleClientset(t.object)
 }
 
+func (t TestKubernetesConnector) GetInstanceID() string {
+       return ""
+}
+
 func TestCreateService(t *testing.T) {
        name := "mock-service"
        testCases := []struct {