diff options
Diffstat (limited to 'src/k8splugin')
21 files changed, 615 insertions, 60 deletions
diff --git a/src/k8splugin/api/api.go b/src/k8splugin/api/api.go index cb094683..2c53a297 100644 --- a/src/k8splugin/api/api.go +++ b/src/k8splugin/api/api.go @@ -17,6 +17,7 @@ package api import ( "github.com/onap/multicloud-k8s/src/k8splugin/internal/app" "github.com/onap/multicloud-k8s/src/k8splugin/internal/connection" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/healthcheck" "github.com/onap/multicloud-k8s/src/k8splugin/internal/rb" "github.com/gorilla/mux" @@ -28,7 +29,8 @@ func NewRouter(defClient rb.DefinitionManager, instClient app.InstanceManager, configClient app.ConfigManager, connectionClient connection.ConnectionManager, - templateClient rb.ConfigTemplateManager) *mux.Router { + templateClient rb.ConfigTemplateManager, + healthcheckClient healthcheck.InstanceHCManager) *mux.Router { router := mux.NewRouter() @@ -48,6 +50,11 @@ func NewRouter(defClient rb.DefinitionManager, instRouter.HandleFunc("/instance/{instID}", instHandler.getHandler).Methods("GET") instRouter.HandleFunc("/instance/{instID}/status", instHandler.statusHandler).Methods("GET") + instRouter.HandleFunc("/instance/{instID}/query", instHandler.queryHandler). + Queries("ApiVersion", "{ApiVersion}", + "Kind", "{Kind}", + "Name", "{Name}", + "Labels", "{Labels}").Methods("GET") instRouter.HandleFunc("/instance/{instID}", instHandler.deleteHandler).Methods("DELETE") // (TODO): Fix update method // instRouter.HandleFunc("/{vnfInstanceId}", UpdateHandler).Methods("PUT") @@ -115,6 +122,16 @@ func NewRouter(defClient rb.DefinitionManager, instRouter.HandleFunc("/instance/{instID}/config/rollback", configHandler.rollbackHandler).Methods("POST") instRouter.HandleFunc("/instance/{instID}/config/tagit", configHandler.tagitHandler).Methods("POST") + // Instance Healthcheck API + if healthcheckClient == nil { + healthcheckClient = healthcheck.NewHCClient() + } + healthcheckHandler := instanceHCHandler{client: healthcheckClient} + instRouter.HandleFunc("/instance/{instID}/healthcheck", healthcheckHandler.listHandler).Methods("GET") + instRouter.HandleFunc("/instance/{instID}/healthcheck", healthcheckHandler.createHandler).Methods("POST") + instRouter.HandleFunc("/instance/{instID}/healthcheck/{hcID}", healthcheckHandler.getHandler).Methods("GET") + instRouter.HandleFunc("/instance/{instID}/healthcheck/{hcID}", healthcheckHandler.deleteHandler).Methods("DELETE") + // Add healthcheck path instRouter.HandleFunc("/healthcheck", healthCheckHandler).Methods("GET") diff --git a/src/k8splugin/api/brokerhandler_test.go b/src/k8splugin/api/brokerhandler_test.go index c822f6d1..97c8a393 100644 --- a/src/k8splugin/api/brokerhandler_test.go +++ b/src/k8splugin/api/brokerhandler_test.go @@ -313,7 +313,7 @@ func TestBrokerCreateHandler(t *testing.T) { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("POST", "/cloudowner/cloudregion/infra_workload", testCase.input) - resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil)) defer resp.Body.Close() if testCase.expectedCode != resp.StatusCode { @@ -409,7 +409,7 @@ func TestBrokerGetHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("GET", "/cloudowner/cloudregion/infra_workload/"+testCase.input, nil) - resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil)) if testCase.expectedCode != resp.StatusCode { t.Fatalf("Request method returned: %v and it was expected: %v", @@ -489,7 +489,7 @@ func TestBrokerFindHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("GET", "/cloudowner/cloudregion/infra_workload?name="+testCase.input, nil) - resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil)) if testCase.expectedCode != resp.StatusCode { t.Fatalf("Request method returned: %v and it was expected: %v", @@ -551,7 +551,7 @@ func TestBrokerDeleteHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("DELETE", "/cloudowner/cloudregion/infra_workload/"+testCase.input, nil) - resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil)) if testCase.expectedCode != resp.StatusCode { t.Fatalf("Request method returned: %v and it was expected: %v", resp.StatusCode, testCase.expectedCode) diff --git a/src/k8splugin/api/defhandler_test.go b/src/k8splugin/api/defhandler_test.go index dcfea1de..bb2f9dc4 100644 --- a/src/k8splugin/api/defhandler_test.go +++ b/src/k8splugin/api/defhandler_test.go @@ -139,7 +139,7 @@ func TestRBDefCreateHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("POST", "/v1/rb/definition", testCase.reader) - resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -208,7 +208,7 @@ func TestRBDefListVersionsHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("GET", "/v1/rb/definition/testresourcebundle", nil) - resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -288,7 +288,7 @@ func TestRBDefListAllHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("GET", "/v1/rb/definition", nil) - resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -368,7 +368,7 @@ func TestRBDefGetHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("GET", "/v1/rb/definition/"+testCase.name+"/"+testCase.version, nil) - resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -419,7 +419,7 @@ func TestRBDefDeleteHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("DELETE", "/v1/rb/definition/"+testCase.name+"/"+testCase.version, nil) - resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -476,7 +476,7 @@ func TestRBDefUploadHandler(t *testing.T) { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("POST", "/v1/rb/definition/"+testCase.name+"/"+testCase.version+"/content", testCase.body) - resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { diff --git a/src/k8splugin/api/healthcheckhandler_test.go b/src/k8splugin/api/healthcheckhandler_test.go index ab9322fd..293ddf91 100644 --- a/src/k8splugin/api/healthcheckhandler_test.go +++ b/src/k8splugin/api/healthcheckhandler_test.go @@ -35,7 +35,7 @@ func TestHealthCheckHandler(t *testing.T) { Err: nil, } request := httptest.NewRequest("GET", "/v1/healthcheck", nil) - resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != http.StatusOK { @@ -48,7 +48,7 @@ func TestHealthCheckHandler(t *testing.T) { Err: pkgerrors.New("Runtime Error in DB"), } request := httptest.NewRequest("GET", "/v1/healthcheck", nil) - resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != http.StatusInternalServerError { diff --git a/src/k8splugin/api/instancehandler.go b/src/k8splugin/api/instancehandler.go index b0437426..b56a8e12 100644 --- a/src/k8splugin/api/instancehandler.go +++ b/src/k8splugin/api/instancehandler.go @@ -1,5 +1,6 @@ /* Copyright 2018 Intel Corporation. +Copyright © 2021 Samsung Electronics 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 @@ -171,6 +172,51 @@ func (i instanceHandler) statusHandler(w http.ResponseWriter, r *http.Request) { } } +// queryHandler retrieves information about specified resources for instance +func (i instanceHandler) queryHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id := vars["instID"] + apiVersion := r.FormValue("ApiVersion") + kind := r.FormValue("Kind") + name := r.FormValue("Name") + labels := r.FormValue("Labels") + if apiVersion == "" { + http.Error(w, "Missing apiVersion mandatory parameter", http.StatusBadRequest) + return + } + if kind == "" { + http.Error(w, "Missing kind mandatory parameter", http.StatusBadRequest) + return + } + if name == "" && labels == "" { + http.Error(w, "Name or Labels parameter must be provided", http.StatusBadRequest) + return + } + resp, err := i.client.Query(id, apiVersion, kind, name, labels) + if err != nil { + log.Error("Error getting Query results", log.Fields{ + "error": err, + "id": id, + "apiVersion": apiVersion, + "kind": kind, + "name": name, + "labels": labels, + }) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(resp) + if err != nil { + log.Error("Error Marshaling Response", log.Fields{ + "error": err, + "response": resp, + }) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + // listHandler retrieves information about an instance via the ID func (i instanceHandler) listHandler(w http.ResponseWriter, r *http.Request) { diff --git a/src/k8splugin/api/instancehandler_test.go b/src/k8splugin/api/instancehandler_test.go index c0690fb2..8e6c72fc 100644 --- a/src/k8splugin/api/instancehandler_test.go +++ b/src/k8splugin/api/instancehandler_test.go @@ -194,7 +194,7 @@ func TestInstanceCreateHandler(t *testing.T) { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("POST", "/v1/instance", testCase.input) - resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil)) if testCase.expectedCode != resp.StatusCode { body, _ := ioutil.ReadAll(resp.Body) @@ -295,7 +295,7 @@ func TestInstanceGetHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("GET", "/v1/instance/"+testCase.input, nil) - resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil)) if testCase.expectedCode != resp.StatusCode { t.Fatalf("Request method returned: %v and it was expected: %v", @@ -430,7 +430,7 @@ func TestInstanceListHandler(t *testing.T) { } request.URL.RawQuery = q.Encode() } - resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil)) if testCase.expectedCode != resp.StatusCode { t.Fatalf("Request method returned: %v and it was expected: %v", @@ -489,7 +489,7 @@ func TestDeleteHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("DELETE", "/v1/instance/"+testCase.input, nil) - resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil)) if testCase.expectedCode != resp.StatusCode { t.Fatalf("Request method returned: %v and it was expected: %v", resp.StatusCode, testCase.expectedCode) diff --git a/src/k8splugin/api/instancehchandler.go b/src/k8splugin/api/instancehchandler.go new file mode 100644 index 00000000..fc1c3be4 --- /dev/null +++ b/src/k8splugin/api/instancehchandler.go @@ -0,0 +1,68 @@ +/* +Copyright © 2021 Samsung Electronics +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 api + +import ( + "encoding/json" + "net/http" + + "github.com/onap/multicloud-k8s/src/k8splugin/internal/healthcheck" + log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils" + + "github.com/gorilla/mux" +) + +// Used to store the backend implementation objects +// Also simplifies the mocking needed for unit testing +type instanceHCHandler struct { + // Interface that implements Healthcheck operations + client healthcheck.InstanceHCManager +} + +func (ih instanceHCHandler) createHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id := vars["instID"] + + resp, err := ih.client.Create(id) + if err != nil { + log.Error("Error scheduling healhtcheck", log.Fields{ + "error": err, + "instance": id, + "response": resp, + }) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(resp) + if err != nil { + log.Error("Error Marshaling Reponse", log.Fields{ + "error": err, + "response": resp, + }) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func (ih instanceHCHandler) getHandler(w http.ResponseWriter, r *http.Request) { +} + +func (ih instanceHCHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { +} + +func (ih instanceHCHandler) listHandler(w http.ResponseWriter, r *http.Request) { +} diff --git a/src/k8splugin/api/profilehandler_test.go b/src/k8splugin/api/profilehandler_test.go index 9ec9c54c..6897e01d 100644 --- a/src/k8splugin/api/profilehandler_test.go +++ b/src/k8splugin/api/profilehandler_test.go @@ -127,7 +127,7 @@ func TestRBProfileCreateHandler(t *testing.T) { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("POST", "/v1/rb/definition/test-rbdef/v1/profile", testCase.reader) - resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -207,7 +207,7 @@ func TestRBProfileGetHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("GET", "/v1/rb/definition/test-rbdef/v1/profile/"+testCase.prname, nil) - resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -288,7 +288,7 @@ func TestRBProfileListHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("GET", "/v1/rb/definition/"+testCase.def+"/"+testCase.version+"/profile", nil) - resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -347,7 +347,7 @@ func TestRBProfileDeleteHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("DELETE", "/v1/rb/definition/test-rbdef/v1/profile/"+testCase.prname, nil) - resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -400,7 +400,7 @@ func TestRBProfileUploadHandler(t *testing.T) { t.Run(testCase.label, func(t *testing.T) { request := httptest.NewRequest("POST", "/v1/rb/definition/test-rbdef/v1/profile/"+testCase.prname+"/content", testCase.body) - resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil)) + resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { diff --git a/src/k8splugin/cmd/main.go b/src/k8splugin/cmd/main.go index 8f9ffc56..2b7346bb 100644 --- a/src/k8splugin/cmd/main.go +++ b/src/k8splugin/cmd/main.go @@ -1,5 +1,6 @@ /* Copyright 2018 Intel Corporation. +Copyright © 2021 Samsung Electronics 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 @@ -39,7 +40,7 @@ func main() { rand.Seed(time.Now().UnixNano()) - httpRouter := api.NewRouter(nil, nil, nil, nil, nil, nil) + httpRouter := api.NewRouter(nil, nil, nil, nil, nil, nil, nil) loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter) log.Println("Starting Kubernetes Multicloud API") diff --git a/src/k8splugin/go.sum b/src/k8splugin/go.sum index af489130..17d143aa 100644 --- a/src/k8splugin/go.sum +++ b/src/k8splugin/go.sum @@ -241,6 +241,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onap/multicloud-k8s v0.0.0-20210224130448-2f09583725c8 h1:eJ6xWCP7UmTqn+5Z8emFxI0rH/EOPzWupxw0G7hBkxs= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go index 6762d1bc..85fefe69 100644 --- a/src/k8splugin/internal/app/client.go +++ b/src/k8splugin/internal/app/client.go @@ -1,6 +1,6 @@ /* Copyright 2018 Intel Corporation. -Copyright © 2020 Samsung Electronics +Copyright © 2021 Samsung Electronics Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ limitations under the License. package app import ( + "io/ioutil" "os" "strings" "time" @@ -32,9 +33,11 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/client-go/restmapper" "k8s.io/client-go/tools/clientcmd" ) @@ -42,6 +45,8 @@ import ( // KubernetesClient encapsulates the different clients' interfaces // we need when interacting with a Kubernetes cluster type KubernetesClient struct { + rawConfig clientcmd.ClientConfig + restConfig *rest.Config clientSet kubernetes.Interface dynamicClient dynamic.Interface discoverClient *disk.CachedDiscoveryClient @@ -90,6 +95,39 @@ func (k *KubernetesClient) getPodsByLabel(namespace string) ([]ResourceStatus, e return resp, nil } +func (k *KubernetesClient) queryResources(apiVersion, kind, labelSelector, namespace string) ([]ResourceStatus, error) { + dynClient := k.GetDynamicClient() + mapper := k.GetMapper() + gvk := schema.FromAPIVersionAndKind(apiVersion, kind) + mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + return nil, pkgerrors.Wrap(err, "Preparing mapper based on GVK") + } + + gvr := mapping.Resource + opts := metav1.ListOptions{ + LabelSelector: labelSelector, + } + var unstrList *unstructured.UnstructuredList + switch mapping.Scope.Name() { + case meta.RESTScopeNameNamespace: + unstrList, err = dynClient.Resource(gvr).Namespace(namespace).List(opts) + case meta.RESTScopeNameRoot: + unstrList, err = dynClient.Resource(gvr).List(opts) + default: + return nil, pkgerrors.New("Got an unknown RESTScopeName for mapping: " + gvk.String()) + } + if err != nil { + return nil, pkgerrors.Wrap(err, "Querying for resources") + } + + resp := make([]ResourceStatus, len(unstrList.Items)) + for _, unstr := range unstrList.Items { + resp = append(resp, ResourceStatus{unstr.GetName(), gvk, unstr}) + } + return resp, nil +} + // getResourcesStatus yields status of given generic resource func (k *KubernetesClient) getResourceStatus(res helm.KubernetesResource, namespace string) (ResourceStatus, error) { dynClient := k.GetDynamicClient() @@ -135,8 +173,8 @@ func (k *KubernetesClient) getKubeConfig(cloudregion string) (string, error) { return kubeConfigPath, nil } -// init loads the Kubernetes configuation values stored into the local configuration file -func (k *KubernetesClient) init(cloudregion string, iid string) error { +// Init loads the Kubernetes configuation values stored into the local configuration file +func (k *KubernetesClient) Init(cloudregion string, iid string) error { if cloudregion == "" { return pkgerrors.New("Cloudregion is empty") } @@ -176,6 +214,21 @@ func (k *KubernetesClient) init(cloudregion string, iid string) error { } k.restMapper = restmapper.NewDeferredDiscoveryRESTMapper(k.discoverClient) + k.restConfig = config + + //Spawn ClientConfig + kubeFile, err := os.Open(configPath) + if err != nil { + return pkgerrors.Wrap(err, "Opening kubeConfig") + } + kubeData, err := ioutil.ReadAll(kubeFile) + if err != nil { + return pkgerrors.Wrap(err, "Reading kubeConfig") + } + k.rawConfig, err = clientcmd.NewClientConfigFromBytes(kubeData) + if err != nil { + return pkgerrors.Wrap(err, "Creating rawConfig") + } return nil } @@ -390,3 +443,18 @@ func (k *KubernetesClient) GetStandardClient() kubernetes.Interface { func (k *KubernetesClient) GetInstanceID() string { return k.instanceID } + +//Following set of methods are implemented so that KubernetesClient +//implements genericclioptions.RESTClientGetter interface +func (k *KubernetesClient) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + return k.discoverClient, nil +} +func (k *KubernetesClient) ToRESTMapper() (meta.RESTMapper, error) { + return k.GetMapper(), nil +} +func (k *KubernetesClient) ToRawKubeConfigLoader() clientcmd.ClientConfig { + return k.rawConfig +} +func (k *KubernetesClient) ToRESTConfig() (*rest.Config, error) { + return k.restConfig, nil +} diff --git a/src/k8splugin/internal/app/client_test.go b/src/k8splugin/internal/app/client_test.go index 7001d9e2..6db541a4 100644 --- a/src/k8splugin/internal/app/client_test.go +++ b/src/k8splugin/internal/app/client_test.go @@ -72,7 +72,7 @@ func TestInit(t *testing.T) { kubeClient := KubernetesClient{} // Refer to the connection via its name - err = kubeClient.init("mock_connection", "abcdefg") + err = kubeClient.Init("mock_connection", "abcdefg") if err != nil { t.Fatalf("TestGetKubeClient returned an error (%s)", err) } diff --git a/src/k8splugin/internal/app/config_backend.go b/src/k8splugin/internal/app/config_backend.go index 5771c83f..e2f802c7 100644 --- a/src/k8splugin/internal/app/config_backend.go +++ b/src/k8splugin/internal/app/config_backend.go @@ -391,7 +391,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, inst.ID) + 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 @@ -418,7 +418,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, inst.ID) + 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 @@ -488,7 +488,7 @@ var resolve = func(rbName, rbVersion, profileName string, p Config) (configResou profile.ReleaseName) chartPath := filepath.Join(chartBasePath, t.ChartName) - resTemplates, err = helmClient.GenerateKubernetesArtifacts(chartPath, + resTemplates, _, err = helmClient.GenerateKubernetesArtifacts(chartPath, []string{outputfile.Name()}, nil) if err != nil { diff --git a/src/k8splugin/internal/app/instance.go b/src/k8splugin/internal/app/instance.go index 69ade3a8..337ce687 100644 --- a/src/k8splugin/internal/app/instance.go +++ b/src/k8splugin/internal/app/instance.go @@ -22,6 +22,8 @@ import ( "log" "strings" + protorelease "k8s.io/helm/pkg/proto/hapi/release" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm" "github.com/onap/multicloud-k8s/src/k8splugin/internal/namegenerator" @@ -49,6 +51,7 @@ type InstanceResponse struct { Namespace string `json:"namespace"` ReleaseName string `json:"release-name"` Resources []helm.KubernetesResource `json:"resources"` + Hooks []*protorelease.Hook `json:"hooks"` } // InstanceMiniResponse contains the response from instantiation @@ -74,6 +77,7 @@ type InstanceManager interface { Create(i InstanceRequest) (InstanceResponse, error) Get(id string) (InstanceResponse, error) Status(id string) (InstanceStatus, error) + Query(id, apiVersion, kind, name, labels string) (InstanceStatus, error) List(rbname, rbversion, profilename string) ([]InstanceMiniResponse, error) Find(rbName string, ver string, profile string, labelKeys map[string]string) ([]InstanceMiniResponse, error) Delete(id string) error @@ -146,7 +150,7 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { } //Execute the kubernetes create command - sortedTemplates, releaseName, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues, i.ReleaseName) + sortedTemplates, hookList, releaseName, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues, i.ReleaseName) if err != nil { return InstanceResponse{}, pkgerrors.Wrap(err, "Error resolving helm charts") } @@ -155,7 +159,7 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { id := namegenerator.Generate() k8sClient := KubernetesClient{} - err = k8sClient.init(i.CloudRegion, id) + err = k8sClient.Init(i.CloudRegion, id) if err != nil { return InstanceResponse{}, pkgerrors.Wrap(err, "Getting CloudRegion Information") } @@ -172,6 +176,7 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { Namespace: profile.Namespace, ReleaseName: releaseName, Resources: createdResources, + Hooks: hookList, } key := InstanceKey{ @@ -208,6 +213,68 @@ func (v *InstanceClient) Get(id string) (InstanceResponse, error) { return InstanceResponse{}, pkgerrors.New("Error getting Instance") } +// Query returns state of instance's filtered resources +func (v *InstanceClient) Query(id, apiVersion, kind, name, labels string) (InstanceStatus, error) { + + //Read the status from the DB + key := InstanceKey{ + ID: id, + } + value, err := db.DBconn.Read(v.storeName, key, v.tagInst) + if err != nil { + return InstanceStatus{}, pkgerrors.Wrap(err, "Get Instance") + } + if value == nil { //value is a byte array + return InstanceStatus{}, pkgerrors.New("Status is not available") + } + resResp := InstanceResponse{} + err = db.DBconn.Unmarshal(value, &resResp) + if err != nil { + return InstanceStatus{}, pkgerrors.Wrap(err, "Unmarshaling Instance Value") + } + + k8sClient := KubernetesClient{} + err = k8sClient.Init(resResp.Request.CloudRegion, id) + if err != nil { + return InstanceStatus{}, pkgerrors.Wrap(err, "Getting CloudRegion Information") + } + + var resourcesStatus []ResourceStatus + if labels != "" { + resList, err := k8sClient.queryResources(apiVersion, kind, labels, resResp.Namespace) + if err != nil { + return InstanceStatus{}, pkgerrors.Wrap(err, "Querying Resources") + } + // If user specifies both label and name, we want to pick up only single resource from these matching label + if name != "" { + //Assigning 0-length, because we may actually not find matching name + resourcesStatus = make([]ResourceStatus, 0) + for _, res := range resList { + if res.Name == name { + resourcesStatus = append(resourcesStatus, res) + break + } + } + } else { + resourcesStatus = resList + } + } else if name != "" { + resIdentifier := helm.KubernetesResource{} + res, err := k8sClient.getResourceStatus(resIdentifier, resResp.Namespace) + if err != nil { + return InstanceStatus{}, pkgerrors.Wrap(err, "Querying Resource") + } + resourcesStatus = []ResourceStatus{res} + } + + resp := InstanceStatus{ + Request: resResp.Request, + ResourceCount: int32(len(resourcesStatus)), + ResourcesStatus: resourcesStatus, + } + return resp, nil +} + // Status returns the status for the instance func (v *InstanceClient) Status(id string) (InstanceStatus, error) { @@ -233,7 +300,7 @@ func (v *InstanceClient) Status(id string) (InstanceStatus, error) { } k8sClient := KubernetesClient{} - err = k8sClient.init(resResp.Request.CloudRegion, id) + err = k8sClient.Init(resResp.Request.CloudRegion, id) if err != nil { return InstanceStatus{}, pkgerrors.Wrap(err, "Getting CloudRegion Information") } @@ -369,7 +436,7 @@ func (v *InstanceClient) Delete(id string) error { } k8sClient := KubernetesClient{} - err = k8sClient.init(inst.Request.CloudRegion, inst.ID) + err = k8sClient.Init(inst.Request.CloudRegion, inst.ID) if err != nil { return pkgerrors.Wrap(err, "Getting CloudRegion Information") } diff --git a/src/k8splugin/internal/app/instance_test.go b/src/k8splugin/internal/app/instance_test.go index b79cf388..2711a52f 100644 --- a/src/k8splugin/internal/app/instance_test.go +++ b/src/k8splugin/internal/app/instance_test.go @@ -179,7 +179,7 @@ func TestInstanceCreate(t *testing.T) { log.Println(ir) if len(ir.Resources) == 0 { - t.Fatalf("TestInstanceCreate returned empty data (%s)", ir) + t.Fatalf("TestInstanceCreate returned empty data (%+v)", ir) } }) diff --git a/src/k8splugin/internal/healthcheck/healthcheck.go b/src/k8splugin/internal/healthcheck/healthcheck.go new file mode 100644 index 00000000..341b1dba --- /dev/null +++ b/src/k8splugin/internal/healthcheck/healthcheck.go @@ -0,0 +1,157 @@ +/* +Copyright © 2021 Samsung Electronics +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 healthcheck + +import ( + "encoding/json" + + protorelease "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/releasetesting" + + "github.com/onap/multicloud-k8s/src/k8splugin/internal/app" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" + log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/namegenerator" + + pkgerrors "github.com/pkg/errors" +) + +// HealthcheckState holds possible states of Healthcheck instance +type HealthcheckState string + +const ( + HcS_UNKNOWN HealthcheckState = "UNKNOWN" + HcS_STARTED HealthcheckState = "STARTED" + HcS_RUNNING HealthcheckState = "RUNNING" + HcS_SUCCESS HealthcheckState = "SUCCESS" + HcS_FAILURE HealthcheckState = "FAILURE" +) + +// InstanceHCManager interface exposes instance Healthcheck fuctionalities +type InstanceHCManager interface { + Create(instanceId string) (InstanceHCStatus, error) + Get(instanceId, healthcheckId string) (InstanceHCStatus, error) + List(instanceId string) ([]InstanceHCStatus, error) + Delete(instanceId, healthcheckId string) error +} + +// HealthcheckKey is used as the primary key in the db +type HealthcheckKey struct { + InstanceId string `json:"instance-id"` + HealthcheckId string `json:"healthcheck-id"` +} + +// We will use json marshalling to convert to string to +// preserve the underlying structure. +func (dk HealthcheckKey) String() string { + out, err := json.Marshal(dk) + if err != nil { + return "" + } + + return string(out) +} + +// InstanceHCClient implements InstanceHCManager +type InstanceHCClient struct { + storeName string + tagInst string +} + +// InstanceHCStatus holds healthcheck status +type InstanceHCStatus struct { + releasetesting.TestSuite + Id string + Status HealthcheckState +} + +func NewHCClient() *InstanceHCClient { + return &InstanceHCClient{ + storeName: "rbdef", + tagInst: "instanceHC", + } +} + +func (ihc InstanceHCClient) Create(instanceId string) (InstanceHCStatus, error) { + //Generate ID + id := namegenerator.Generate() + + //Determine Cloud Region and namespace + v := app.NewInstanceClient() + instance, err := v.Get(instanceId) + if err != nil { + return InstanceHCStatus{}, pkgerrors.Wrap(err, "Getting instance") + } + + //Prepare Environment, Request and Release structs + //TODO In future could derive params from request + client, err := NewKubeClient(instanceId, instance.Request.CloudRegion) + if err != nil { + return InstanceHCStatus{}, pkgerrors.Wrap(err, "Preparing KubeClient") + } + env := &releasetesting.Environment{ + Namespace: instance.Namespace, + KubeClient: client, + Parallel: false, + } + release := protorelease.Release{ + Name: instance.ReleaseName, + Hooks: instance.Hooks, + } + + //Run HC + testSuite, err := releasetesting.NewTestSuite(&release) + if err != nil { + log.Error("Error creating TestSuite", log.Fields{ + "Release": release, + }) + return InstanceHCStatus{}, pkgerrors.Wrap(err, "Creating TestSuite") + } + if err = testSuite.Run(env); err != nil { + log.Error("Error running TestSuite", log.Fields{ + "TestSuite": testSuite, + "Environment": env, + }) + return InstanceHCStatus{}, pkgerrors.Wrap(err, "Running TestSuite") + } + + //Save state + ihcs := InstanceHCStatus{ + TestSuite: *testSuite, + Id: id, + Status: HcS_STARTED, + } + key := HealthcheckKey{ + InstanceId: instance.ID, + HealthcheckId: id, + } + err = db.DBconn.Create(ihc.storeName, key, ihc.tagInst, ihcs) + if err != nil { + return ihcs, pkgerrors.Wrap(err, "Creating Instance DB Entry") + } + + return ihcs, nil +} + +func (ihc InstanceHCClient) Get(instanceId, healthcheckId string) (InstanceHCStatus, error) { + return InstanceHCStatus{}, nil +} + +func (ihc InstanceHCClient) Delete(instanceId, healthcheckId string) error { + return nil +} + +func (ihc InstanceHCClient) List(instanceId string) ([]InstanceHCStatus, error) { + return make([]InstanceHCStatus, 0), nil +} diff --git a/src/k8splugin/internal/healthcheck/kubeclient.go b/src/k8splugin/internal/healthcheck/kubeclient.go new file mode 100644 index 00000000..be4c6fcc --- /dev/null +++ b/src/k8splugin/internal/healthcheck/kubeclient.go @@ -0,0 +1,59 @@ +/* +Copyright © 2021 Samsung Electronics +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 healthcheck + +import ( + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/tiller/environment" + + "github.com/onap/multicloud-k8s/src/k8splugin/internal/app" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/config" + + pkgerrors "github.com/pkg/errors" +) + +//implements environment.KubeClient but overrides it so that +//custom labels can be injected into created resources +// using internal k8sClient +type KubeClientImpl struct { + environment.KubeClient + labels map[string]string + k app.KubernetesClient +} + +func NewKubeClient(instanceId, cloudRegion string) (*KubeClientImpl, error) { + k8sClient := app.KubernetesClient{} + err := k8sClient.Init(cloudRegion, instanceId) + if err != nil { + return nil, pkgerrors.Wrap(err, "Initializing k8sClient") + } + return &KubeClientImpl{ + labels: map[string]string{ + config.GetConfiguration().KubernetesLabelName: instanceId, + }, + KubeClient: kube.New(&k8sClient), + k: k8sClient, + }, nil +} + +/* FIXME +// Need to correct this later and provide override of Create method to use our k8sClient +// So that healthcheck hook resources would be labeled with vf-module data just like currently +// every k8splugin-managed resource is + +//Create function is overrided to label test resources with custom labels +func (kci *KubeClientImpl) Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error { + return nil +} +*/ diff --git a/src/k8splugin/internal/helm/helm.go b/src/k8splugin/internal/helm/helm.go index d3715fce..d39e8404 100644 --- a/src/k8splugin/internal/helm/helm.go +++ b/src/k8splugin/internal/helm/helm.go @@ -1,5 +1,6 @@ /* * Copyright 2018 Intel Corporation, Inc + * Copyright © 2021 Samsung Electronics * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +25,7 @@ import ( "path/filepath" "regexp" "sort" + "strconv" "strings" utils "github.com/onap/multicloud-k8s/src/k8splugin/internal" @@ -35,8 +37,10 @@ import ( "k8s.io/apimachinery/pkg/util/validation" k8syaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/manifest" "k8s.io/helm/pkg/proto/hapi/chart" + protorelease "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/releaseutil" "k8s.io/helm/pkg/renderutil" "k8s.io/helm/pkg/tiller" @@ -46,6 +50,7 @@ import ( // Template is the interface for all helm templating commands // Any backend implementation will implement this interface and will // access the functionality via this. +// FIXME Template is not referenced anywhere type Template interface { GenerateKubernetesArtifacts( chartPath string, @@ -74,6 +79,12 @@ func NewTemplateClient(k8sversion, namespace, releasename string) *TemplateClien } } +// Define hooks that are honored by k8splugin +var honoredEvents = map[string]protorelease.Hook_Event{ + hooks.ReleaseTestSuccess: protorelease.Hook_RELEASE_TEST_SUCCESS, + hooks.ReleaseTestFailure: protorelease.Hook_RELEASE_TEST_FAILURE, +} + // Combines valueFiles and values into a single values stream. // values takes precedence over valueFiles func (h *TemplateClient) processValues(valueFiles []string, values []string) ([]byte, error) { @@ -138,12 +149,60 @@ func (h *TemplateClient) mergeValues(dest map[string]interface{}, src map[string return dest } +// Checks whether resource is a hook and if it is, returns hook struct +//Logic is based on private method +//file *manifestFile) sort(result *result) error +//of helm/pkg/tiller package +func isHook(path, resource string) (*protorelease.Hook, error) { + + var entry releaseutil.SimpleHead + err := yaml.Unmarshal([]byte(resource), &entry) + if err != nil { + return nil, pkgerrors.Wrap(err, "Loading resource to YAML") + } + //If resource has no metadata it can't be a hook + if entry.Metadata == nil || + entry.Metadata.Annotations == nil || + len(entry.Metadata.Annotations) == 0 { + return nil, nil + } + //Determine hook weight + hookWeight, err := strconv.Atoi(entry.Metadata.Annotations[hooks.HookWeightAnno]) + if err != nil { + hookWeight = 0 + } + //Prepare hook obj + resultHook := &protorelease.Hook{ + Name: entry.Metadata.Name, + Kind: entry.Kind, + Path: path, + Manifest: resource, + Events: []protorelease.Hook_Event{}, + Weight: int32(hookWeight), + DeletePolicies: []protorelease.Hook_DeletePolicy{}, + } + //Determine hook's events + hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno] + if !ok { + return resultHook, nil + } + for _, hookType := range strings.Split(hookTypes, ",") { + hookType = strings.ToLower(strings.TrimSpace(hookType)) + e, ok := honoredEvents[hookType] + if ok { + resultHook.Events = append(resultHook.Events, e) + } + } + return resultHook, nil +} + // GenerateKubernetesArtifacts a mapping of type to fully evaluated helm template func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFiles []string, - values []string) ([]KubernetesResourceTemplate, error) { + values []string) ([]KubernetesResourceTemplate, []*protorelease.Hook, error) { var outputDir, chartPath, namespace, releaseName string var retData []KubernetesResourceTemplate + var hookList []*protorelease.Hook releaseName = h.releaseName namespace = h.kubeNameSpace @@ -151,16 +210,16 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile // verify chart path exists if _, err := os.Stat(inputPath); err == nil { if chartPath, err = filepath.Abs(inputPath); err != nil { - return retData, err + return retData, hookList, err } } else { - return retData, err + return retData, hookList, err } //Create a temp directory in the system temp folder outputDir, err := ioutil.TempDir("", "helm-tmpl-") if err != nil { - return retData, pkgerrors.Wrap(err, "Got error creating temp dir") + return retData, hookList, pkgerrors.Wrap(err, "Got error creating temp dir") } if namespace == "" { @@ -170,18 +229,18 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile // get combined values and create config rawVals, err := h.processValues(valueFiles, values) if err != nil { - return retData, err + return retData, hookList, err } config := &chart.Config{Raw: string(rawVals), Values: map[string]*chart.Value{}} if msgs := validation.IsDNS1123Label(releaseName); releaseName != "" && len(msgs) > 0 { - return retData, fmt.Errorf("release name %s is not a valid DNS label: %s", releaseName, strings.Join(msgs, ";")) + return retData, hookList, fmt.Errorf("release name %s is not a valid DNS label: %s", releaseName, strings.Join(msgs, ";")) } // Check chart requirements to make sure all dependencies are present in /charts c, err := chartutil.Load(chartPath) if err != nil { - return retData, pkgerrors.Errorf("Got error: %s", err.Error()) + return retData, hookList, pkgerrors.Errorf("Got error: %s", err.Error()) } renderOpts := renderutil.Options{ @@ -197,7 +256,7 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile renderedTemplates, err := renderutil.Render(c, config, renderOpts) if err != nil { - return retData, err + return retData, hookList, err } newRenderedTemplates := make(map[string]string) @@ -246,16 +305,24 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile continue } + hook, _ := isHook(b, data) + // if hook is not nil, then append it to hooks list and continue + // if it's not, disregard error + if hook != nil { + hookList = append(hookList, hook) + continue + } + mfilePath := filepath.Join(outputDir, m.Name) utils.EnsureDirectory(mfilePath) err = ioutil.WriteFile(mfilePath, []byte(data), 0666) if err != nil { - return retData, err + return retData, hookList, err } gvk, err := getGroupVersionKind(data) if err != nil { - return retData, err + return retData, hookList, err } kres := KubernetesResourceTemplate{ @@ -264,7 +331,7 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile } retData = append(retData, kres) } - return retData, nil + return retData, hookList, nil } func getGroupVersionKind(data string) (schema.GroupVersionKind, error) { diff --git a/src/k8splugin/internal/helm/helm_test.go b/src/k8splugin/internal/helm/helm_test.go index d25ca091..358577ea 100644 --- a/src/k8splugin/internal/helm/helm_test.go +++ b/src/k8splugin/internal/helm/helm_test.go @@ -200,7 +200,7 @@ func TestGenerateKubernetesArtifacts(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { tc := NewTemplateClient("1.12.3", "testnamespace", "testreleasename") - out, err := tc.GenerateKubernetesArtifacts(testCase.chartPath, testCase.valueFiles, + out, _, err := tc.GenerateKubernetesArtifacts(testCase.chartPath, testCase.valueFiles, testCase.values) if err != nil { if testCase.expectedError == "" { diff --git a/src/k8splugin/internal/rb/profile.go b/src/k8splugin/internal/rb/profile.go index f8b07abf..df4d2e5c 100644 --- a/src/k8splugin/internal/rb/profile.go +++ b/src/k8splugin/internal/rb/profile.go @@ -1,5 +1,6 @@ /* * Copyright 2018 Intel Corporation, Inc + * Copyright © 2021 Samsung Electronics * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +24,8 @@ import ( "log" "path/filepath" + protorelease "k8s.io/helm/pkg/proto/hapi/release" + "github.com/onap/multicloud-k8s/src/k8splugin/internal/db" "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm" @@ -270,49 +273,50 @@ func (v *ProfileClient) Download(rbName, rbVersion, prName string) ([]byte, erro //Resolve returns the path where the helm chart merged with //configuration overrides resides and final ReleaseName picked for instantiation func (v *ProfileClient) Resolve(rbName string, rbVersion string, - profileName string, values []string, overrideReleaseName string) ([]helm.KubernetesResourceTemplate, string, error) { + profileName string, values []string, overrideReleaseName string) ([]helm.KubernetesResourceTemplate, []*protorelease.Hook, string, error) { var sortedTemplates []helm.KubernetesResourceTemplate + var hookList []*protorelease.Hook var finalReleaseName string //Download and process the profile first //If everything seems okay, then download the definition prData, err := v.Download(rbName, rbVersion, profileName) if err != nil { - return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Downloading Profile") + return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Downloading Profile") } prPath, err := ExtractTarBall(bytes.NewBuffer(prData)) if err != nil { - return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Extracting Profile Content") + return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Extracting Profile Content") } prYamlClient, err := ProcessProfileYaml(prPath, v.manifestName) if err != nil { - return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Processing Profile Manifest") + return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Processing Profile Manifest") } definitionClient := NewDefinitionClient() definition, err := definitionClient.Get(rbName, rbVersion) if err != nil { - return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Getting Definition Metadata") + return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Getting Definition Metadata") } defData, err := definitionClient.Download(rbName, rbVersion) if err != nil { - return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Downloading Definition") + return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Downloading Definition") } chartBasePath, err := ExtractTarBall(bytes.NewBuffer(defData)) if err != nil { - return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Extracting Definition Charts") + return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Extracting Definition Charts") } //Get the definition ID and download its contents profile, err := v.Get(rbName, rbVersion, profileName) if err != nil { - return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Getting Profile") + return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Getting Profile") } //Copy the profile configresources to the chart locations @@ -322,7 +326,7 @@ func (v *ProfileClient) Resolve(rbName string, rbVersion string, // chartpath: chart/config/resources/config.yaml err = prYamlClient.CopyConfigurationOverrides(chartBasePath) if err != nil { - return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Copying configresources to chart") + return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Copying configresources to chart") } if overrideReleaseName == "" { @@ -336,14 +340,14 @@ func (v *ProfileClient) Resolve(rbName string, rbVersion string, finalReleaseName) chartPath := filepath.Join(chartBasePath, definition.ChartName) - sortedTemplates, err = helmClient.GenerateKubernetesArtifacts(chartPath, + sortedTemplates, hookList, err = helmClient.GenerateKubernetesArtifacts(chartPath, []string{prYamlClient.GetValues()}, values) if err != nil { - return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Generate final k8s yaml") + return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Generate final k8s yaml") } - return sortedTemplates, finalReleaseName, nil + return sortedTemplates, hookList, finalReleaseName, nil } // Returns an empty profile with the following contents diff --git a/src/k8splugin/internal/rb/profile_test.go b/src/k8splugin/internal/rb/profile_test.go index a434e5a1..3c40c2c9 100644 --- a/src/k8splugin/internal/rb/profile_test.go +++ b/src/k8splugin/internal/rb/profile_test.go @@ -773,7 +773,7 @@ func TestResolveProfile(t *testing.T) { t.Run(testCase.label, func(t *testing.T) { db.DBconn = testCase.mockdb impl := NewProfileClient() - data, releaseName, err := impl.Resolve(testCase.rbname, + data, _, releaseName, err := impl.Resolve(testCase.rbname, testCase.rbversion, testCase.prname, []string{}, testCase.releaseName) defer cleanup(data) if err != nil { |