From 81c8ffaa3046245caf3aff5bffe2b971d497ac3d Mon Sep 17 00:00:00 2001 From: Manjunath Ranganathaiah Date: Fri, 19 Jun 2020 17:54:58 +0000 Subject: Instantiation and termination of a given context implementation. Issue-ID: MULTICLOUD-1005 Signed-off-by: Manjunath Ranganathaiah Change-Id: I60e11aaad97b60efc24a02866dc0e580507e5296 --- src/rsync/cmd/main.go | 4 +- src/rsync/go.mod | 12 +- src/rsync/go.sum | 5 + src/rsync/pkg/app/client.go | 154 ++++++ src/rsync/pkg/connector/connector.go | 96 ++++ src/rsync/pkg/context/context.go | 547 +++++++++++++++++++++ .../pkg/grpc/installappserver/installappserver.go | 27 +- src/rsync/pkg/internal/config/config.go | 128 +++++ src/rsync/pkg/internal/utils.go | 59 +++ src/rsync/pkg/resource/resource.go | 129 +++++ 10 files changed, 1144 insertions(+), 17 deletions(-) create mode 100644 src/rsync/pkg/app/client.go create mode 100644 src/rsync/pkg/connector/connector.go create mode 100644 src/rsync/pkg/context/context.go create mode 100644 src/rsync/pkg/internal/config/config.go create mode 100644 src/rsync/pkg/internal/utils.go create mode 100644 src/rsync/pkg/resource/resource.go diff --git a/src/rsync/cmd/main.go b/src/rsync/cmd/main.go index f46fa79b..95c36e20 100644 --- a/src/rsync/cmd/main.go +++ b/src/rsync/cmd/main.go @@ -81,7 +81,6 @@ func main() { // Initialize the mongodb err := db.InitializeDatabaseConnection("mco") if err != nil { - fmt.Println(" Exiting mongod ") log.Println("Unable to initialize database connection...") log.Println(err) log.Fatalln("Exiting...") @@ -90,14 +89,13 @@ func main() { // Initialize contextdb err = contextDb.InitializeContextDatabase() if err != nil { - fmt.Println(" Exiting etcd") log.Println("Unable to initialize database connection...") log.Println(err) log.Fatalln("Exiting...") } // Start grpc - fmt.Println("starting rsync GRPC server..") + log.Println("starting rsync GRPC server..") err = startGrpcServer() if err != nil { log.Fatalf("GRPC server failed to start") diff --git a/src/rsync/go.mod b/src/rsync/go.mod index 9c3362ce..a2c5f83b 100644 --- a/src/rsync/go.mod +++ b/src/rsync/go.mod @@ -3,13 +3,15 @@ module github.com/onap/multicloud-k8s/src/rsync go 1.13 require ( - github.com/golang/protobuf v1.3.4 + github.com/golang/protobuf v1.4.1 + github.com/googleapis/gnostic v0.4.0 + github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.9.5 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/onap/multicloud-k8s/src/orchestrator v0.0.0-20200601021239-7959bd4c6fd4 - golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 // indirect - google.golang.org/appengine v1.4.0 // indirect + go.etcd.io/bbolt v1.3.3 // indirect google.golang.org/grpc v1.27.1 - honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc // indirect - github.com/googleapis/gnostic v0.4.0 k8s.io/kubernetes v1.14.1 ) diff --git a/src/rsync/go.sum b/src/rsync/go.sum index 270417c2..c49c5b07 100644 --- a/src/rsync/go.sum +++ b/src/rsync/go.sum @@ -742,6 +742,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2El google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -765,6 +767,9 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/src/rsync/pkg/app/client.go b/src/rsync/pkg/app/client.go new file mode 100644 index 00000000..fb57d46b --- /dev/null +++ b/src/rsync/pkg/app/client.go @@ -0,0 +1,154 @@ +/* +Copyright 2018 Intel Corporation. +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. +*/ + +package app + +import ( + "os" + "strings" + "time" + "encoding/base64" + + pkgerrors "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/discovery/cached/disk" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/restmapper" + "k8s.io/client-go/tools/clientcmd" + + "github.com/onap/multicloud-k8s/src/clm/pkg/cluster" +) + +const basePath string = "/tmp/rsync/" + +// KubernetesClient encapsulates the different clients' interfaces +// we need when interacting with a Kubernetes cluster +type KubernetesClient struct { + clientSet kubernetes.Interface + dynamicClient dynamic.Interface + discoverClient *disk.CachedDiscoveryClient + restMapper meta.RESTMapper + instanceID string +} + +// getKubeConfig uses the connectivity client to get the kubeconfig based on the name +// of the clustername. This is written out to a file. +func (k *KubernetesClient) getKubeConfig(clustername string, id string) (string, error) { + + if !strings.Contains(clustername, "+") { + return "", pkgerrors.New("Not a valid cluster name") + } + strs := strings.Split(clustername, "+") + if len(strs) != 2 { + return "", pkgerrors.New("Not a valid cluster name") + } + kubeConfig, err := cluster.NewClusterClient().GetClusterContent(strs[0], strs[1]) + if err != nil { + return "", pkgerrors.New("Get kubeconfig failed") + } + + var kubeConfigPath string = basePath + id + "/" + clustername + "/" + + if _, err := os.Stat(kubeConfigPath); os.IsNotExist(err) { + err = os.MkdirAll(kubeConfigPath, 0755) + if err != nil { + return "", err + } + } + kubeConfigPath = kubeConfigPath + "config" + + f, err := os.Create(kubeConfigPath) + defer f.Close() + if err != nil { + return "", err + } + dec, err := base64.StdEncoding.DecodeString(kubeConfig.Kubeconfig) + if err != nil { + return "", err + } + _, err = f.Write(dec) + if err != nil { + return "", err + } + + return kubeConfigPath, nil +} + +// init loads the Kubernetes configuation values stored into the local configuration file +func (k *KubernetesClient) Init(clustername string, iid string) error { + if clustername == "" { + return pkgerrors.New("Cloudregion is empty") + } + + if iid == "" { + return pkgerrors.New("Instance ID is empty") + } + + k.instanceID = iid + + configPath, err := k.getKubeConfig(clustername, iid) + if err != nil { + return pkgerrors.Wrap(err, "Get kubeconfig file") + } + + //Remove kubeconfigfile after the clients are created + defer os.Remove(configPath) + + config, err := clientcmd.BuildConfigFromFlags("", configPath) + if err != nil { + return pkgerrors.Wrap(err, "setConfig: Build config from flags raised an error") + } + + k.clientSet, err = kubernetes.NewForConfig(config) + if err != nil { + return err + } + + k.dynamicClient, err = dynamic.NewForConfig(config) + if err != nil { + return pkgerrors.Wrap(err, "Creating dynamic client") + } + + k.discoverClient, err = disk.NewCachedDiscoveryClientForConfig(config, os.TempDir(), "", 10*time.Minute) + if err != nil { + return pkgerrors.Wrap(err, "Creating discovery client") + } + + k.restMapper = restmapper.NewDeferredDiscoveryRESTMapper(k.discoverClient) + + return nil +} + +//GetMapper returns the RESTMapper that was created for this client +func (k *KubernetesClient) GetMapper() meta.RESTMapper { + return k.restMapper +} + +//GetDynamicClient returns the dynamic client that is needed for +//unstructured REST calls to the apiserver +func (k *KubernetesClient) GetDynamicClient() dynamic.Interface { + return k.dynamicClient +} + +// GetStandardClient returns the standard client that can be used to handle +// standard kubernetes kinds +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 +} diff --git a/src/rsync/pkg/connector/connector.go b/src/rsync/pkg/connector/connector.go new file mode 100644 index 00000000..6e17f87a --- /dev/null +++ b/src/rsync/pkg/connector/connector.go @@ -0,0 +1,96 @@ +/* + * Copyright 2019 Intel Corporation, Inc + * + * 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. + */ + +package connector + +import ( + "log" + + "github.com/onap/multicloud-k8s/src/rsync/pkg/internal/config" + + 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" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" +) + +// KubernetesConnector is an interface that is expected to be implemented +// by any code that calls the plugin framework functions. +// It implements methods that are needed by the plugins to get Kubernetes +// clients and other information needed to interface with Kubernetes +type KubernetesConnector interface { + //GetMapper returns the RESTMapper that was created for this client + GetMapper() meta.RESTMapper + + //GetDynamicClient returns the dynamic client that is needed for + //unstructured REST calls to the apiserver + GetDynamicClient() dynamic.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 +type Reference interface { + //Create a kubernetes resource described by the yaml in yamlFilePath + Create(yamlFilePath string, namespace string, client KubernetesConnector) (string, error) + //Delete a kubernetes resource described in the provided namespace + Delete(yamlFilePath string, resname string, namespace string, client KubernetesConnector) error +} + +// 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 + } + + //Attempt to convert the template to a podtemplatespec. + //This is to check if we have any pods being created. + podTemplateSpec := &corev1.PodTemplateSpec{} + err := runtime.DefaultUnstructuredConverter.FromUnstructured(template, podTemplateSpec) + if err != nil { + log.Println("Did not find a podTemplateSpec: " + err.Error()) + return + } + + labels := podTemplateSpec.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels[config.GetConfiguration().KubernetesLabelName] = tag + podTemplateSpec.SetLabels(labels) + + updatedTemplate, err := runtime.DefaultUnstructuredConverter.ToUnstructured(podTemplateSpec) + + //Set the label + spec["template"] = updatedTemplate +} diff --git a/src/rsync/pkg/context/context.go b/src/rsync/pkg/context/context.go new file mode 100644 index 00000000..67e589c9 --- /dev/null +++ b/src/rsync/pkg/context/context.go @@ -0,0 +1,547 @@ +/* + * Copyright 2020 Intel Corporation, Inc + * + * 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. + */ + +package context + +import ( + "encoding/json" + "fmt" + "log" + "os" + "sync" + "strings" + "github.com/onap/multicloud-k8s/src/orchestrator/pkg/appcontext" + pkgerrors "github.com/pkg/errors" + res "github.com/onap/multicloud-k8s/src/rsync/pkg/resource" + con "github.com/onap/multicloud-k8s/src/rsync/pkg/connector" + "github.com/onap/multicloud-k8s/src/rsync/pkg/app" +) + +type CompositeAppContext struct { + cid interface{} + appsorder string + appsdependency string + appsmap []instMap +} +type clusterInfo struct { + name string + resorder string + resdependency string + ressmap []instMap +} +type instMap struct { + name string + depinfo string + status string + rerr error + clusters []clusterInfo +} + +const basePath string = "/tmp/rsync/" + +func getInstMap(order string, dependency string, level string) ([]instMap, error) { + + if order == "" { + return nil, pkgerrors.Errorf("Not a valid order value") + } + if dependency == "" { + return nil, pkgerrors.Errorf("Not a valid dependency value") + } + + if !(level == "app" || level == "res") { + return nil, pkgerrors.Errorf("Not a valid level name given to create map") + } + + + var aov map[string]interface{} + json.Unmarshal([]byte(order), &aov) + + s := fmt.Sprintf("%vorder", level) + appso := aov[s].([]interface{}) + var instmap = make([]instMap, len(appso)) + + var adv map[string]interface{} + json.Unmarshal([]byte(dependency), &adv) + s = fmt.Sprintf("%vdependency", level) + appsd := adv[s].(map[string]interface{}) + for i, u := range appso { + instmap[i] = instMap{u.(string), appsd[u.(string)].(string), "none", nil, nil} + } + + return instmap, nil +} + +func deleteResource(clustername string, resname string, respath string) error { + k8sClient := app.KubernetesClient{} + err := k8sClient.Init(clustername, resname) + if err != nil { + log.Println("Init failed: " + err.Error()) + return err + } + + var c con.KubernetesConnector + c = &k8sClient + var gp res.Resource + err = gp.Delete(respath, resname, "default", c) + if err != nil { + log.Println("Delete resource failed: " + err.Error()) + return err + } + log.Println("Resource succesfully deleted") + return nil + +} + +func createResource(clustername string, resname string, respath string) error { + k8sClient := app.KubernetesClient{} + err := k8sClient.Init(clustername, resname) + if err != nil { + log.Println("Client init failed: " + err.Error()) + return err + } + + var c con.KubernetesConnector + c = &k8sClient + var gp res.Resource + _, err = gp.Create(respath,"default", c) + if err != nil { + log.Println("Create failed: " + err.Error()) + return err + } + log.Println("Resource succesfully created") + return nil + +} + +func terminateResource(ac appcontext.AppContext, resmap instMap, appname string, clustername string) error { + var resPath string = basePath + appname + "/" + clustername + "/resources/" + rh, err := ac.GetResourceHandle(appname, clustername, resmap.name) + if err != nil { + return err + } + + resval, err := ac.GetValue(rh) + if err != nil { + return err + } + + if resval != "" { + if _, err := os.Stat(resPath); os.IsNotExist(err) { + err = os.MkdirAll(resPath, 0755) + if err != nil { + return err + } + } + resPath := resPath + resmap.name + ".yaml" + f, err := os.Create(resPath) + defer f.Close() + if err != nil { + return err + } + _, err = f.WriteString(resval.(string)) + if err != nil { + return err + } + result := strings.Split(resmap.name, "+") + if result[0] == "" { + return pkgerrors.Errorf("Resource name is nil") + } + err = deleteResource(clustername, result[0], resPath) + if err != nil { + return err + } + //defer os.Remove(resPath) + } else { + return pkgerrors.Errorf("Resource value is nil") + } + + return nil + +} + +func instantiateResource(ac appcontext.AppContext, resmap instMap, appname string, clustername string) error { + var resPath string = basePath + appname + "/" + clustername + "/resources/" + rh, err := ac.GetResourceHandle(appname, clustername, resmap.name) + if err != nil { + return err + } + + resval, err := ac.GetValue(rh) + if err != nil { + return err + } + + if resval != "" { + if _, err := os.Stat(resPath); os.IsNotExist(err) { + err = os.MkdirAll(resPath, 0755) + if err != nil { + return err + } + } + resPath := resPath + resmap.name + ".yaml" + f, err := os.Create(resPath) + defer f.Close() + if err != nil { + return err + } + _, err = f.WriteString(resval.(string)) + if err != nil { + return err + } + result := strings.Split(resmap.name, "+") + if result[0] == "" { + return pkgerrors.Errorf("Resource name is nil") + } + err = createResource(clustername, result[0], resPath) + if err != nil { + return err + } + //defer os.Remove(resPath) + } else { + return pkgerrors.Errorf("Resource value is nil") + } + + return nil + +} + +func terminateResources(ac appcontext.AppContext, ressmap []instMap, appname string, clustername string) error { + var wg sync.WaitGroup + var chans = make([]chan int, len(ressmap)) + for l := range chans { + chans[l] = make(chan int) + } + for i:=0; i