From 10337dd73e55ecf6327515e70a60aef4325af589 Mon Sep 17 00:00:00 2001 From: rsood Date: Wed, 10 Apr 2019 05:00:04 +0000 Subject: Day 2 Configuration API's This patch adds Configuration API's https://wiki.onap.org/display/DW/MultiCloud+K8s-Plugin-service+API%27s Change-Id: I52ebfc5aa980ec8af4a31569d569216e9a2a760c Issue-ID: MULTICLOUD-464 Signed-off-by: rsood --- src/k8splugin/api/api.go | 26 +- src/k8splugin/api/confighandler.go | 210 +++++++++++++ src/k8splugin/api/configtemplatehandler.go | 134 ++++++++ src/k8splugin/api/defhandler_test.go | 10 +- src/k8splugin/api/instancehandler_test.go | 6 +- src/k8splugin/api/profilehandler_test.go | 8 +- src/k8splugin/cmd/main.go | 2 +- src/k8splugin/go.mod | 2 + src/k8splugin/go.sum | 4 + src/k8splugin/internal/db/etcd.go | 131 ++++++++ src/k8splugin/internal/db/etcd_testing.go | 45 +++ src/k8splugin/internal/rb/config.go | 436 ++++++++++++++++++++++++++ src/k8splugin/internal/rb/config_backend.go | 439 +++++++++++++++++++++++++++ src/k8splugin/internal/rb/config_template.go | 257 ++++++++++++++++ src/k8splugin/internal/rb/config_test.go | 259 ++++++++++++++++ src/k8splugin/internal/utils.go | 11 + 16 files changed, 1966 insertions(+), 14 deletions(-) create mode 100644 src/k8splugin/api/confighandler.go create mode 100644 src/k8splugin/api/configtemplatehandler.go create mode 100644 src/k8splugin/internal/db/etcd.go create mode 100644 src/k8splugin/internal/db/etcd_testing.go create mode 100644 src/k8splugin/internal/rb/config.go create mode 100644 src/k8splugin/internal/rb/config_backend.go create mode 100644 src/k8splugin/internal/rb/config_template.go create mode 100644 src/k8splugin/internal/rb/config_test.go (limited to 'src/k8splugin') diff --git a/src/k8splugin/api/api.go b/src/k8splugin/api/api.go index 54147d2e..2003a809 100644 --- a/src/k8splugin/api/api.go +++ b/src/k8splugin/api/api.go @@ -23,7 +23,9 @@ import ( // NewRouter creates a router that registers the various urls that are supported func NewRouter(defClient rb.DefinitionManager, profileClient rb.ProfileManager, - instClient app.InstanceManager) *mux.Router { + instClient app.InstanceManager, + configClient rb.ConfigManager, + templateClient rb.ConfigTemplateManager) *mux.Router { router := mux.NewRouter() @@ -61,5 +63,27 @@ func NewRouter(defClient rb.DefinitionManager, resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}", profileHandler.getHandler).Methods("GET") resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}", profileHandler.deleteHandler).Methods("DELETE") + // Config Template + if templateClient == nil { + templateClient = rb.NewConfigTemplateClient() + } + templateHandler := rbTemplateHandler{client: templateClient} + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/config-template", templateHandler.createHandler).Methods("POST") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/config-template/{tname}/content", templateHandler.uploadHandler).Methods("POST") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/config-template/{tname}", templateHandler.getHandler).Methods("GET") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/config-template/{tname}", templateHandler.deleteHandler).Methods("DELETE") + + // Config value + if configClient == nil { + configClient = rb.NewConfigClient() + } + configHandler := rbConfigHandler{client: configClient} + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}/config", configHandler.createHandler).Methods("POST") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}/config/{cfgname}", configHandler.getHandler).Methods("GET") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}/config/{cfgname}", configHandler.updateHandler).Methods("PUT") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}/config/{cfgname}", configHandler.deleteHandler).Methods("DELETE") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}/config/rollback", configHandler.rollbackHandler).Methods("POST") + resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}/config/tagit", configHandler.tagitHandler).Methods("POST") + return router } diff --git a/src/k8splugin/api/confighandler.go b/src/k8splugin/api/confighandler.go new file mode 100644 index 00000000..93098d61 --- /dev/null +++ b/src/k8splugin/api/confighandler.go @@ -0,0 +1,210 @@ +/* + * Copyright 2018 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 api + +import ( + "encoding/json" + "k8splugin/internal/rb" + "net/http" + + "github.com/gorilla/mux" +) + +// Used to store backend implementations objects +// Also simplifies mocking for unit testing purposes +type rbConfigHandler struct { + // Interface that implements bundle Definition operations + // We will set this variable with a mock interface for testing + client rb.ConfigManager +} + +// createHandler handles creation of the definition entry in the database +func (h rbConfigHandler) createHandler(w http.ResponseWriter, r *http.Request) { + var p rb.Config + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + prName := vars["prname"] + + if r.Body == nil { + http.Error(w, "Empty body", http.StatusBadRequest) + return + } + + err := json.NewDecoder(r.Body).Decode(&p) + if err != nil { + http.Error(w, err.Error(), http.StatusUnprocessableEntity) + return + } + + // Name is required. + if p.ConfigName == "" { + http.Error(w, "Missing name in POST request", http.StatusBadRequest) + return + } + + ret, err := h.client.Create(rbName, rbVersion, prName, p) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(ret) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +// getHandler handles GET operations on a particular config +// Returns a rb.Definition +func (h rbConfigHandler) getHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + prName := vars["prname"] + cfgName := vars["cfgname"] + + ret, err := h.client.Get(rbName, rbVersion, prName, cfgName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(ret) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +// deleteHandler handles DELETE operations on a config +func (h rbConfigHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + prName := vars["prname"] + cfgName := vars["cfgname"] + + ret, err := h.client.Delete(rbName, rbVersion, prName, cfgName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(ret) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + +} + +// UpdateHandler handles Update operations on a particular configuration +func (h rbConfigHandler) updateHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + prName := vars["prname"] + cfgName := vars["cfgname"] + + var p rb.Config + + if r.Body == nil { + http.Error(w, "Empty body", http.StatusBadRequest) + return + } + + err := json.NewDecoder(r.Body).Decode(&p) + if err != nil { + http.Error(w, err.Error(), http.StatusUnprocessableEntity) + return + } + + ret, err := h.client.Update(rbName, rbVersion, prName, cfgName, p) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + err = json.NewEncoder(w).Encode(ret) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +// rollbackHandler handles Rollback operations to a specific version +func (h rbConfigHandler) rollbackHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + prName := vars["prname"] + + if r.Body == nil { + http.Error(w, "Empty body", http.StatusBadRequest) + return + } + + var p rb.ConfigRollback + err := json.NewDecoder(r.Body).Decode(&p) + if err != nil { + http.Error(w, err.Error(), http.StatusUnprocessableEntity) + return + } + err = h.client.Rollback(rbName, rbVersion, prName, p) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusNoContent) +} + +// tagitHandler handles TAGIT operation +func (h rbConfigHandler) tagitHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + prName := vars["prname"] + + if r.Body == nil { + http.Error(w, "Empty body", http.StatusBadRequest) + return + } + + var p rb.ConfigTagit + err := json.NewDecoder(r.Body).Decode(&p) + if err != nil { + http.Error(w, err.Error(), http.StatusUnprocessableEntity) + return + } + + err = h.client.Tagit(rbName, rbVersion, prName, p) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusNoContent) +} diff --git a/src/k8splugin/api/configtemplatehandler.go b/src/k8splugin/api/configtemplatehandler.go new file mode 100644 index 00000000..a91165cb --- /dev/null +++ b/src/k8splugin/api/configtemplatehandler.go @@ -0,0 +1,134 @@ +/* + * Copyright 2018 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 api + +import ( + "encoding/json" + "io" + "io/ioutil" + "k8splugin/internal/rb" + "net/http" + + "github.com/gorilla/mux" +) + +// Used to store backend implementations objects +// Also simplifies mocking for unit testing purposes +type rbTemplateHandler struct { + // Interface that implements bundle Definition operations + // We will set this variable with a mock interface for testing + client rb.ConfigTemplateManager +} + +// createHandler handles creation of the template entry in the database +func (h rbTemplateHandler) createHandler(w http.ResponseWriter, r *http.Request) { + var p rb.ConfigTemplate + + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + + err := json.NewDecoder(r.Body).Decode(&p) + switch { + case err == io.EOF: + http.Error(w, "Empty body", http.StatusBadRequest) + return + case err != nil: + http.Error(w, err.Error(), http.StatusUnprocessableEntity) + return + } + + // Name is required. + if p.TemplateName == "" { + http.Error(w, "Missing name in POST request", http.StatusBadRequest) + return + } + + err = h.client.Create(rbName, rbVersion, p) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) + +} + +// uploadHandler handles upload of the template tar file into the database +func (h rbTemplateHandler) uploadHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + templateName := vars["tname"] + + inpBytes, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Unable to read body", http.StatusBadRequest) + return + } + + if len(inpBytes) == 0 { + http.Error(w, "Empty body", http.StatusBadRequest) + return + } + + err = h.client.Upload(rbName, rbVersion, templateName, inpBytes) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} + +// getHandler handles GET operations on a particular template +func (h rbTemplateHandler) getHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + templateName := vars["tname"] + + ret, err := h.client.Get(rbName, rbVersion, templateName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + err = json.NewEncoder(w).Encode(ret) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +// deleteHandler handles DELETE operations on a template +func (h rbTemplateHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rbName := vars["rbname"] + rbVersion := vars["rbversion"] + templateName := vars["tname"] + + err := h.client.Delete(rbName, rbVersion, templateName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) +} diff --git a/src/k8splugin/api/defhandler_test.go b/src/k8splugin/api/defhandler_test.go index 03189e1d..321eb460 100644 --- a/src/k8splugin/api/defhandler_test.go +++ b/src/k8splugin/api/defhandler_test.go @@ -138,7 +138,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)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -207,7 +207,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)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -287,7 +287,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)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -338,7 +338,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)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -395,7 +395,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)) + resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { diff --git a/src/k8splugin/api/instancehandler_test.go b/src/k8splugin/api/instancehandler_test.go index d01d5dfb..ed7135a6 100644 --- a/src/k8splugin/api/instancehandler_test.go +++ b/src/k8splugin/api/instancehandler_test.go @@ -137,7 +137,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)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil)) if testCase.expectedCode != resp.StatusCode { body, _ := ioutil.ReadAll(resp.Body) @@ -210,7 +210,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)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil)) if testCase.expectedCode != resp.StatusCode { t.Fatalf("Request method returned: %v and it was expected: %v", @@ -257,7 +257,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)) + resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, 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/profilehandler_test.go b/src/k8splugin/api/profilehandler_test.go index 932a0247..2473fea5 100644 --- a/src/k8splugin/api/profilehandler_test.go +++ b/src/k8splugin/api/profilehandler_test.go @@ -117,7 +117,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)) + resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -188,7 +188,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)) + resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -236,7 +236,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)) + resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil)) //Check returned code if resp.StatusCode != testCase.expectedCode { @@ -289,7 +289,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)) + resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, 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 e600c0d6..96e1c8e5 100644 --- a/src/k8splugin/cmd/main.go +++ b/src/k8splugin/cmd/main.go @@ -37,7 +37,7 @@ func main() { rand.Seed(time.Now().UnixNano()) - httpRouter := api.NewRouter(nil, nil, nil) + httpRouter := api.NewRouter(nil, nil, nil, nil, nil) loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter) log.Println("Starting Kubernetes Multicloud API") diff --git a/src/k8splugin/go.mod b/src/k8splugin/go.mod index 29a10ecb..59c7a173 100644 --- a/src/k8splugin/go.mod +++ b/src/k8splugin/go.mod @@ -8,6 +8,7 @@ require ( github.com/Masterminds/sprig v2.17.1+incompatible // indirect github.com/aokoli/goutils v1.1.0 // indirect github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect + github.com/coreos/etcd v3.3.12+incompatible // indirect github.com/cyphar/filepath-securejoin v0.2.2 // indirect github.com/docker/distribution v2.7.0+incompatible // indirect github.com/docker/docker v0.7.3-0.20190312165151-258edd715d46 // indirect @@ -66,6 +67,7 @@ require ( github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51 // indirect github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect github.com/xdg/stringprep v1.0.0 // indirect + go.etcd.io/etcd v3.3.12+incompatible go.mongodb.org/mongo-driver v1.0.0 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 // indirect diff --git a/src/k8splugin/go.sum b/src/k8splugin/go.sum index f22fe8da..453a45d9 100644 --- a/src/k8splugin/go.sum +++ b/src/k8splugin/go.sum @@ -20,6 +20,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 h1:HD4PLRzjuCVW79mQ0/pdsalOLHJ+FaEoqJLxfltpb2U= github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/etcd v3.3.12+incompatible h1:5k8nkcBSvltjOO5RLflnXevOJXndlKIMbvVnMTX+cUU= +github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -177,6 +179,8 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +go.etcd.io/etcd v3.3.12+incompatible h1:xR2YQOYo5JV5BMrUj9i1kcf2rEbpCQKHH2sKTtpAHiQ= +go.etcd.io/etcd v3.3.12+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.mongodb.org/mongo-driver v1.0.0 h1:KxPRDyfB2xXnDE2My8acoOWBQkfv3tz0SaWTRZjJR0c= go.mongodb.org/mongo-driver v1.0.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4 h1:wviDUSmtheHRBfoY8B9U8ELl2USoXi2YFwdGdpIIkzI= diff --git a/src/k8splugin/internal/db/etcd.go b/src/k8splugin/internal/db/etcd.go new file mode 100644 index 00000000..fda44b2f --- /dev/null +++ b/src/k8splugin/internal/db/etcd.go @@ -0,0 +1,131 @@ +/* + * Copyright 2018 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 db + +import ( + "context" + "time" + + pkgerrors "github.com/pkg/errors" + "go.etcd.io/etcd/clientv3" + "go.etcd.io/etcd/pkg/transport" +) + +// EtcdConfig Configuration values needed for Etcd Client +type EtcdConfig struct { + Endpoint string + CertFile string + KeyFile string + CAFile string +} + +// EtcdStore Interface needed for mocking +type EtcdStore interface { + Get(key string) ([]byte, error) + Put(key, value string) error + Delete(key string) error +} + +// EtcdClient for Etcd +type EtcdClient struct { + cli *clientv3.Client +} + +// Etcd handle for interface +var Etcd EtcdStore + +// NewEtcdClient function initializes Etcd client +func NewEtcdClient(store *clientv3.Client, c EtcdConfig) error { + var err error + Etcd, err = newClient(store, c) + return err +} + +func newClient(store *clientv3.Client, c EtcdConfig) (EtcdClient, error) { + if store == nil { + tlsInfo := transport.TLSInfo{ + CertFile: c.CertFile, + KeyFile: c.KeyFile, + CAFile: c.CAFile, + } + tlsConfig, err := tlsInfo.ClientConfig() + if err != nil { + return EtcdClient{}, pkgerrors.Errorf("Error creating etcd TLSInfo: %s", err.Error()) + } + // NOTE: Client relies on nil tlsConfig + // for non-secure connections, update the implicit variable + if len(c.CertFile) == 0 && len(c.KeyFile) == 0 && len(c.CAFile) == 0 { + tlsConfig = nil + } + endpoint := "https://" + c.Endpoint + ":2379" + + store, err = clientv3.New(clientv3.Config{ + Endpoints: []string{endpoint}, + DialTimeout: 5 * time.Second, + TLS: tlsConfig, + }) + if err != nil { + return EtcdClient{}, pkgerrors.Errorf("Error creating etcd client: %s", err.Error()) + } + } + + return EtcdClient{ + cli: store, + }, nil +} + +// Put values in Etcd DB +func (e EtcdClient) Put(key, value string) error { + + if e.cli == nil { + return pkgerrors.Errorf("Etcd Client not initialized") + } + _, err := e.cli.Put(context.Background(), key, value) + if err != nil { + return pkgerrors.Errorf("Error creating etcd entry: %s", err.Error()) + } + return nil +} + +// Get values from Etcd DB +func (e EtcdClient) Get(key string) ([]byte, error) { + + if e.cli == nil { + return nil, pkgerrors.Errorf("Etcd Client not initialized") + } + getResp, err := e.cli.Get(context.Background(), key) + if err != nil { + return nil, pkgerrors.Errorf("Error getitng etcd entry: %s", err.Error()) + } + if getResp.Count == 0 { + return nil, pkgerrors.Errorf("Key doesn't exist") + } + return getResp.Kvs[0].Value, nil +} + +// Delete values from Etcd DB +func (e EtcdClient) Delete(key string) error { + + if e.cli == nil { + return pkgerrors.Errorf("Etcd Client not initialized") + } + _, err := e.cli.Delete(context.Background(), key) + if err != nil { + return pkgerrors.Errorf("Delete failed etcd entry:%s", err.Error()) + } + return nil +} diff --git a/src/k8splugin/internal/db/etcd_testing.go b/src/k8splugin/internal/db/etcd_testing.go new file mode 100644 index 00000000..12b17e33 --- /dev/null +++ b/src/k8splugin/internal/db/etcd_testing.go @@ -0,0 +1,45 @@ +/* +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 db + +import ( + pkgerrors "github.com/pkg/errors" +) + +type MockEtcdClient struct { + Items map[string]string + Err error +} + +func (c *MockEtcdClient) Put(key, value string) error { + if c.Items == nil { + c.Items = make(map[string]string) + } + c.Items[key] = value + return c.Err +} + +func (c *MockEtcdClient) Get(key string) ([]byte, error) { + for kvKey, kvValue := range c.Items { + if kvKey == key { + return []byte(kvValue), nil + } + } + return nil, pkgerrors.Errorf("Key doesn't exist") +} + +func (c *MockEtcdClient) Delete(key string) error { + delete(c.Items, key) + return c.Err +} diff --git a/src/k8splugin/internal/rb/config.go b/src/k8splugin/internal/rb/config.go new file mode 100644 index 00000000..3bd8347b --- /dev/null +++ b/src/k8splugin/internal/rb/config.go @@ -0,0 +1,436 @@ +/* + * Copyright 2018 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 rb + +import ( + pkgerrors "github.com/pkg/errors" + "k8splugin/internal/db" + "strconv" + "strings" +) + +// Config contains the parameters needed for configuration +type Config struct { + ConfigName string `json:"config-name"` + TemplateName string `json:"template-name"` + Description string `json:"description"` + Values map[string]interface{} `json:"values"` +} + +//ConfigResult output for Create, Update and delete +type ConfigResult struct { + DefinitionName string `json:"rb-name"` + DefinitionVersion string `json:"rb-version"` + ProfileName string `json:"profile-name"` + ConfigName string `json:"config-name"` + TemplateName string `json:"template-name"` + ConfigVersion uint `json:"config-verion"` +} + +//ConfigRollback input +type ConfigRollback struct { + AnyOf struct { + ConfigVersion string `json:"config-version,omitempty"` + ConfigTag string `json:"config-tag,omitempty"` + } `json:"anyOf"` +} + +//ConfigTagit for Tagging configurations +type ConfigTagit struct { + TagName string `json:"tag-name"` +} + +// ConfigManager is an interface exposes the config functionality +type ConfigManager interface { + Create(rbName, rbVersion, profileName string, p Config) (ConfigResult, error) + Get(rbName, rbVersion, profileName, configName string) (Config, error) + Help() map[string]string + Update(rbName, rbVersion, profileName, configName string, p Config) (ConfigResult, error) + Delete(rbName, rbVersion, profileName, configName string) (ConfigResult, error) + Rollback(rbName, rbVersion, profileName string, p ConfigRollback) error + Tagit(rbName, rbVersion, profileName string, p ConfigTagit) error +} + +// ConfigClient implements the ConfigManager +// It will also be used to maintain some localized state +type ConfigClient struct { + tagTag string +} + +// NewConfigClient returns an instance of the ConfigClient +// which implements the ConfigManager +func NewConfigClient() *ConfigClient { + return &ConfigClient{ + tagTag: "tag", + } +} + +// Help returns some information on how to create the content +// for the config in the form of html formatted page +func (v *ConfigClient) Help() map[string]string { + ret := make(map[string]string) + + return ret +} + +// Create an entry for the config in the database +func (v *ConfigClient) Create(rbName, rbVersion, profileName string, p Config) (ConfigResult, error) { + + // Check required fields + if p.ConfigName == "" || p.TemplateName == "" || len(p.Values) == 0 { + return ConfigResult{}, pkgerrors.New("Incomplete Configuration Provided") + } + cs := ConfigStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + configName: p.ConfigName, + } + _, err := cs.getConfig() + if err == nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Create Error - Config exists") + } else { + if strings.Contains(err.Error(), "Key doesn't exist") == false { + return ConfigResult{}, pkgerrors.Wrap(err, "Create Error") + } + } + lock, profileChannel := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + err = applyConfig(rbName, rbVersion, profileName, p, profileChannel, "POST") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") + } + // Create Config DB Entry + err = cs.createConfig(p) + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Create Config DB Entry") + } + // Create Version Entry in DB for Config + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + version, err := cvs.createConfigVersion(p, Config{}, "POST") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Create Config Version DB Entry") + } + // Create Result structure + cfgRes := ConfigResult{ + DefinitionName: rbName, + DefinitionVersion: rbVersion, + ProfileName: profileName, + ConfigName: p.ConfigName, + TemplateName: p.TemplateName, + ConfigVersion: version, + } + return cfgRes, nil +} + +// Update an entry for the config in the database +func (v *ConfigClient) Update(rbName, rbVersion, profileName, configName string, p Config) (ConfigResult, error) { + + // Check required fields + if len(p.Values) == 0 { + return ConfigResult{}, pkgerrors.New("Incomplete Configuration Provided") + } + // Check if Config exists + cs := ConfigStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + configName: configName, + } + _, err := cs.getConfig() + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Update Error - Config doesn't exist") + } + lock, profileChannel := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + err = applyConfig(rbName, rbVersion, profileName, p, profileChannel, "PUT") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") + } + // Update Config DB Entry + configPrev, err := cs.updateConfig(p) + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Update Config DB Entry") + } + // Create Version Entry in DB for Config + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + version, err := cvs.createConfigVersion(p, configPrev, "PUT") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Create Config Version DB Entry") + } + // Create Result structure + cfgRes := ConfigResult{ + DefinitionName: rbName, + DefinitionVersion: rbVersion, + ProfileName: profileName, + ConfigName: p.ConfigName, + TemplateName: p.TemplateName, + ConfigVersion: version, + } + return cfgRes, nil +} + +// Get config entry in the database +func (v *ConfigClient) Get(rbName, rbVersion, profileName, configName string) (Config, error) { + + // Acquire per profile Mutex + lock, _ := getProfileData(rbName + rbVersion + profileName) + lock.Lock() + defer lock.Unlock() + // Read Config DB + cs := ConfigStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + configName: configName, + } + cfg, err := cs.getConfig() + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Get Config DB Entry") + } + return cfg, nil +} + +// Delete the Config from database +func (v *ConfigClient) Delete(rbName, rbVersion, profileName, configName string) (ConfigResult, error) { + + // Check if Config exists + cs := ConfigStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + configName: configName, + } + p, err := cs.getConfig() + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Update Error - Config doesn't exist") + } + lock, profileChannel := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + err = applyConfig(rbName, rbVersion, profileName, p, profileChannel, "DELETE") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Apply Config failed") + } + // Delete Config from DB + configPrev, err := cs.deleteConfig() + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Delete Config DB Entry") + } + // Create Version Entry in DB for Config + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + version, err := cvs.createConfigVersion(Config{}, configPrev, "DELETE") + if err != nil { + return ConfigResult{}, pkgerrors.Wrap(err, "Delete Config Version DB Entry") + } + // Create Result structure + cfgRes := ConfigResult{ + DefinitionName: rbName, + DefinitionVersion: rbVersion, + ProfileName: profileName, + ConfigName: configName, + TemplateName: configPrev.TemplateName, + ConfigVersion: version, + } + return cfgRes, nil +} + +// Rollback starts from current version and rollbacks to the version desired +func (v *ConfigClient) Rollback(rbName, rbVersion, profileName string, rback ConfigRollback) error { + + var reqVersion string + var err error + + if rback.AnyOf.ConfigTag != "" { + reqVersion, err = v.GetTagVersion(rbName, rbVersion, profileName, rback.AnyOf.ConfigTag) + if err != nil { + return pkgerrors.Wrap(err, "Rollback Invalid tag") + } + } else if rback.AnyOf.ConfigVersion != "" { + reqVersion = rback.AnyOf.ConfigVersion + } else { + return pkgerrors.Errorf("No valid Index for Rollback") + } + + index, err := strconv.Atoi(reqVersion) + if err != nil { + return pkgerrors.Wrap(err, "Rollback Invalid Index") + } + rollbackIndex := uint(index) + + lock, profileChannel := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + currentVersion, err := cvs.getCurrentVersion() + if err != nil { + return pkgerrors.Wrap(err, "Rollback Get Current Config Version ") + } + + if rollbackIndex < 1 && rollbackIndex >= currentVersion { + return pkgerrors.Wrap(err, "Rollback Invalid Config Version") + } + + //Rollback all the intermettinent configurations + for i := currentVersion; i > rollbackIndex; i-- { + configNew, configPrev, action, err := cvs.getConfigVersion(i) + if err != nil { + return pkgerrors.Wrap(err, "Rollback Get Config Version") + } + cs := ConfigStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + configName: configNew.ConfigName, + } + if action == "PUT" { + // PUT is proceeded by PUT or POST + err = applyConfig(rbName, rbVersion, profileName, configPrev, profileChannel, "PUT") + if err != nil { + return pkgerrors.Wrap(err, "Apply Config failed") + } + _, err = cs.updateConfig(configPrev) + if err != nil { + return pkgerrors.Wrap(err, "Update Config DB Entry") + } + } else if action == "POST" { + // POST is always preceeded by Config not existing + err = applyConfig(rbName, rbVersion, profileName, configNew, profileChannel, "DELETE") + if err != nil { + return pkgerrors.Wrap(err, "Delete Config failed") + } + _, err = cs.deleteConfig() + if err != nil { + return pkgerrors.Wrap(err, "Delete Config DB Entry") + } + } else if action == "DELETE" { + // DELETE is proceeded by PUT or POST + err = applyConfig(rbName, rbVersion, profileName, configPrev, profileChannel, "PUT") + if err != nil { + return pkgerrors.Wrap(err, "Delete Config failed") + } + _, err = cs.updateConfig(configPrev) + if err != nil { + return pkgerrors.Wrap(err, "Update Config DB Entry") + } + } + } + for i := currentVersion; i > rollbackIndex; i-- { + // Delete rolled back items + err = cvs.deleteConfigVersion() + if err != nil { + return pkgerrors.Wrap(err, "Delete Config Version ") + } + } + return nil +} + +// Tagit tags the current version with the tag provided +func (v *ConfigClient) Tagit(rbName, rbVersion, profileName string, tag ConfigTagit) error { + + lock, _ := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + currentVersion, err := cvs.getCurrentVersion() + if err != nil { + return pkgerrors.Wrap(err, "Get Current Config Version ") + } + tagKey := constructKey(rbName, rbVersion, profileName, v.tagTag, tag.TagName) + + err = db.Etcd.Put(tagKey, strconv.Itoa(int(currentVersion))) + if err != nil { + return pkgerrors.Wrap(err, "TagIt store DB") + } + return nil +} + +// GetTagVersion returns the version associated with the tag +func (v *ConfigClient) GetTagVersion(rbName, rbVersion, profileName, tagName string) (string, error) { + + tagKey := constructKey(rbName, rbVersion, profileName, v.tagTag, tagName) + + value, err := db.Etcd.Get(tagKey) + if err != nil { + return "", pkgerrors.Wrap(err, "Config DB Entry Not found") + } + return string(value), nil +} + +// ApplyAllConfig starts from first configuration version and applies all versions in sequence +func (v *ConfigClient) ApplyAllConfig(rbName, rbVersion, profileName string) error { + + lock, profileChannel := getProfileData(rbName + rbVersion + profileName) + // Acquire per profile Mutex + lock.Lock() + defer lock.Unlock() + + cvs := ConfigVersionStore{ + rbName: rbName, + rbVersion: rbVersion, + profileName: profileName, + } + currentVersion, err := cvs.getCurrentVersion() + if err != nil { + return pkgerrors.Wrap(err, "Get Current Config Version ") + } + if currentVersion < 1 { + return pkgerrors.Wrap(err, "No Config Version to Apply") + } + //Apply all configurations + var i uint + for i = 1; i <= currentVersion; i++ { + configNew, _, action, err := cvs.getConfigVersion(i) + if err != nil { + return pkgerrors.Wrap(err, "Get Config Version") + } + err = applyConfig(rbName, rbVersion, profileName, configNew, profileChannel, action) + if err != nil { + return pkgerrors.Wrap(err, "Apply Config failed") + } + } + return nil +} diff --git a/src/k8splugin/internal/rb/config_backend.go b/src/k8splugin/internal/rb/config_backend.go new file mode 100644 index 00000000..b61fc493 --- /dev/null +++ b/src/k8splugin/internal/rb/config_backend.go @@ -0,0 +1,439 @@ +/* + * Copyright 2018 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 rb + +import ( + "bytes" + "encoding/json" + "k8splugin/internal/db" + "k8splugin/internal/helm" + "log" + "strconv" + "strings" + + "io/ioutil" + "path/filepath" + "sync" + + "github.com/ghodss/yaml" + pkgerrors "github.com/pkg/errors" +) + +//ConfigStore contains the values that will be stored in the database +type configVersionDBContent struct { + ConfigNew Config `json:"config-new"` + ConfigPrev Config `json:"config-prev"` + Action string `json:"action"` // CRUD opration for this config +} + +//ConfigStore to Store the Config +type ConfigStore struct { + rbName string + rbVersion string + profileName string + configName string +} + +//ConfigVersionStore to Store the Versions of the Config +type ConfigVersionStore struct { + rbName string + rbVersion string + profileName string +} + +type configResourceList struct { + retmap map[string][]string + profile Profile + action string +} + +type profileDataManager struct { + profileLockMap map[string]*sync.Mutex + resourceChannel map[string](chan configResourceList) + sync.Mutex +} + +const ( + storeName = "config" + tagCounter = "counter" + tagVersion = "configversion" + tagConfig = "configdata" +) + +var profileData = profileDataManager{ + profileLockMap: map[string]*sync.Mutex{}, + resourceChannel: map[string]chan configResourceList{}, +} + +// Construct key for storing data +func constructKey(strs ...string) string { + + var sb strings.Builder + sb.WriteString("onapk8s") + sb.WriteString("/") + sb.WriteString(storeName) + sb.WriteString("/") + for _, str := range strs { + sb.WriteString(str) + sb.WriteString("/") + } + return sb.String() + +} + +// Create an entry for the config in the database +func (c ConfigStore) createConfig(p Config) error { + + cfgKey := constructKey(c.rbName, c.rbVersion, c.profileName, tagConfig, p.ConfigName) + _, err := db.Etcd.Get(cfgKey) + if err == nil { + return pkgerrors.Wrap(err, "Config DB Entry Already exists") + } + configValue, err := db.Serialize(p) + if err != nil { + return pkgerrors.Wrap(err, "Serialize Config Value") + } + err = db.Etcd.Put(cfgKey, configValue) + if err != nil { + return pkgerrors.Wrap(err, "Config DB Entry") + } + return nil +} + +// Update the config entry in the database. Updates with the new value +// Returns the previous value of the Config +func (c ConfigStore) updateConfig(p Config) (Config, error) { + + cfgKey := constructKey(c.rbName, c.rbVersion, c.profileName, tagConfig, p.ConfigName) + value, err := db.Etcd.Get(cfgKey) + configPrev := Config{} + if err == nil { + // If updating Config after rollback then previous config may not exist + err = db.DeSerialize(string(value), &configPrev) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "DeSerialize Config Value") + } + } + configValue, err := db.Serialize(p) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Serialize Config Value") + } + err = db.Etcd.Put(cfgKey, configValue) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Config DB Entry") + } + return configPrev, nil +} + +// Read the config entry in the database +func (c ConfigStore) getConfig() (Config, error) { + cfgKey := constructKey(c.rbName, c.rbVersion, c.profileName, tagConfig, c.configName) + value, err := db.Etcd.Get(cfgKey) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Get Config DB Entry") + } + //value is a byte array + if value != nil { + cfg := Config{} + err = db.DeSerialize(string(value), &cfg) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Unmarshaling Config Value") + } + return cfg, nil + } + return Config{}, pkgerrors.Wrap(err, "Get Config DB Entry") +} + +// Delete the config entry in the database +func (c ConfigStore) deleteConfig() (Config, error) { + + cfgKey := constructKey(c.rbName, c.rbVersion, c.profileName, tagConfig, c.configName) + value, err := db.Etcd.Get(cfgKey) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Config DB Entry Not found") + } + configPrev := Config{} + err = db.DeSerialize(string(value), &configPrev) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "DeSerialize Config Value") + } + + err = db.Etcd.Delete(cfgKey) + if err != nil { + return Config{}, pkgerrors.Wrap(err, "Config DB Entry") + } + return configPrev, nil +} + +// Create a version for the configuration. If previous config provided that is also stored +func (c ConfigVersionStore) createConfigVersion(configNew, configPrev Config, action string) (uint, error) { + + version, err := c.incrementVersion() + + if err != nil { + return 0, pkgerrors.Wrap(err, "Get Next Version") + } + versionKey := constructKey(c.rbName, c.rbVersion, c.profileName, tagVersion, strconv.Itoa(int(version))) + + var cs configVersionDBContent + cs.Action = action + cs.ConfigNew = configNew + cs.ConfigPrev = configPrev + + configValue, err := db.Serialize(cs) + if err != nil { + return 0, pkgerrors.Wrap(err, "Serialize Config Value") + } + err = db.Etcd.Put(versionKey, configValue) + if err != nil { + return 0, pkgerrors.Wrap(err, "Create Config DB Entry") + } + return version, nil +} + +// Delete current version of the configuration. Configuration always deleted from top +func (c ConfigVersionStore) deleteConfigVersion() error { + + counter, err := c.getCurrentVersion() + + if err != nil { + return pkgerrors.Wrap(err, "Get Next Version") + } + versionKey := constructKey(c.rbName, c.rbVersion, c.profileName, tagVersion, strconv.Itoa(int(counter))) + + err = db.Etcd.Delete(versionKey) + if err != nil { + return pkgerrors.Wrap(err, "Delete Config DB Entry") + } + err = c.decrementVersion() + if err != nil { + return pkgerrors.Wrap(err, "Decrement Version") + } + return nil +} + +// Read the specified version of the configuration and return its prev and current value. +// Also returns the action for the config version +func (c ConfigVersionStore) getConfigVersion(version uint) (Config, Config, string, error) { + + versionKey := constructKey(c.rbName, c.rbVersion, c.profileName, tagVersion, strconv.Itoa(int(version))) + configBytes, err := db.Etcd.Get(versionKey) + if err != nil { + return Config{}, Config{}, "", pkgerrors.Wrap(err, "Get Config Version ") + } + + if configBytes != nil { + pr := configVersionDBContent{} + err = db.DeSerialize(string(configBytes), &pr) + if err != nil { + return Config{}, Config{}, "", pkgerrors.Wrap(err, "DeSerialize Config Version") + } + return pr.ConfigNew, pr.ConfigPrev, pr.Action, nil + } + return Config{}, Config{}, "", pkgerrors.Wrap(err, "Invalid data ") +} + +// Get the counter for the version +func (c ConfigVersionStore) getCurrentVersion() (uint, error) { + + cfgKey := constructKey(c.rbName, c.rbVersion, c.profileName, tagCounter) + + value, err := db.Etcd.Get(cfgKey) + if err != nil { + if strings.Contains(err.Error(), "Key doesn't exist") == true { + // Counter not started yet, 0 is invalid value + return 0, nil + } else { + return 0, pkgerrors.Wrap(err, "Get Current Version") + } + } + + index, err := strconv.Atoi(string(value)) + if err != nil { + return 0, pkgerrors.Wrap(err, "Invalid counter") + } + return uint(index), nil +} + +// Update the counter for the version +func (c ConfigVersionStore) updateVersion(counter uint) error { + + cfgKey := constructKey(c.rbName, c.rbVersion, c.profileName, tagCounter) + err := db.Etcd.Put(cfgKey, strconv.Itoa(int(counter))) + if err != nil { + return pkgerrors.Wrap(err, "Counter DB Entry") + } + return nil +} + +// Increment the version counter +func (c ConfigVersionStore) incrementVersion() (uint, error) { + + counter, err := c.getCurrentVersion() + if err != nil { + return 0, pkgerrors.Wrap(err, "Get Next Counter Value") + } + //This is done while Profile lock is taken + counter++ + err = c.updateVersion(counter) + if err != nil { + return 0, pkgerrors.Wrap(err, "Store Next Counter Value") + } + + return counter, nil +} + +// Decrement the version counter +func (c ConfigVersionStore) decrementVersion() error { + + counter, err := c.getCurrentVersion() + if err != nil { + return pkgerrors.Wrap(err, "Get Next Counter Value") + } + //This is done while Profile lock is taken + counter-- + err = c.updateVersion(counter) + if err != nil { + return pkgerrors.Wrap(err, "Store Next Counter Value") + } + + return nil +} + +// Apply Config +func applyConfig(rbName, rbVersion, profileName string, p Config, pChannel chan configResourceList, action string) error { + + // Get Template and Resolve the template with values + crl, err := resolve(rbName, rbVersion, profileName, p) + if err != nil { + return pkgerrors.Wrap(err, "Resolve Config") + } + crl.action = action + // Send the configResourceList to the channel. Using select for non-blocking channel + select { + case pChannel <- crl: + log.Printf("Message Sent to goroutine %v", crl.profile) + default: + } + + return nil +} + +// Per Profile Go routine to apply the configuration to Cloud Region +func scheduleResources(c chan configResourceList) { + // Keep thread running + for { + data := <-c + //TODO: ADD Check to see if Application running + switch { + case data.action == "POST": + log.Printf("[scheduleResources]: POST %v %v", data.profile, data.retmap) + //TODO: Needs to add code to call Kubectl create + case data.action == "PUT": + log.Printf("[scheduleResources]: PUT %v %v", data.profile, data.retmap) + //TODO: Needs to add code to call Kubectl apply + case data.action == "DELETE": + log.Printf("[scheduleResources]: DELETE %v %v", data.profile, data.retmap) + //TODO: Needs to add code to call Kubectl delete + + } + } +} + +//Resolve returns the path where the helm chart merged with +//configuration overrides resides. +var resolve = func(rbName, rbVersion, profileName string, p Config) (configResourceList, error) { + + var retMap map[string][]string + + profile, err := NewProfileClient().Get(rbName, rbVersion, profileName) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Reading Profile Data") + } + + t, err := NewConfigTemplateClient().Get(rbName, rbVersion, p.TemplateName) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Getting Template") + } + if t.ChartName == "" { + return configResourceList{}, pkgerrors.New("Invalid template no Chart.yaml file found") + } + + def, err := NewConfigTemplateClient().Download(rbName, rbVersion, p.TemplateName) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Downloading Template") + } + + //Create a temp file in the system temp folder for values input + b, err := json.Marshal(p.Values) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Error Marshalling config data") + } + data, err := yaml.JSONToYAML(b) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "JSON to YAML") + } + + outputfile, err := ioutil.TempFile("", "helm-config-values-") + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Got error creating temp file") + } + _, err = outputfile.Write([]byte(data)) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Got error writting temp file") + } + defer outputfile.Close() + + chartBasePath, err := ExtractTarBall(bytes.NewBuffer(def)) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Extracting Template") + } + + helmClient := helm.NewTemplateClient(profile.KubernetesVersion, + profile.Namespace, + profile.ReleaseName) + + chartPath := filepath.Join(chartBasePath, t.ChartName) + retMap, err = helmClient.GenerateKubernetesArtifacts(chartPath, + []string{outputfile.Name()}, + nil) + if err != nil { + return configResourceList{}, pkgerrors.Wrap(err, "Generate final k8s yaml") + } + crl := configResourceList{ + retmap: retMap, + profile: profile, + } + + return crl, nil +} + +// Get the Mutex for the Profile +func getProfileData(key string) (*sync.Mutex, chan configResourceList) { + profileData.Lock() + defer profileData.Unlock() + _, ok := profileData.profileLockMap[key] + if !ok { + profileData.profileLockMap[key] = &sync.Mutex{} + } + _, ok = profileData.resourceChannel[key] + if !ok { + profileData.resourceChannel[key] = make(chan configResourceList) + go scheduleResources(profileData.resourceChannel[key]) + } + return profileData.profileLockMap[key], profileData.resourceChannel[key] +} diff --git a/src/k8splugin/internal/rb/config_template.go b/src/k8splugin/internal/rb/config_template.go new file mode 100644 index 00000000..cdb1b907 --- /dev/null +++ b/src/k8splugin/internal/rb/config_template.go @@ -0,0 +1,257 @@ +/* + * Copyright 2018 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 rb + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "k8splugin/internal/db" + "os" + "path/filepath" + + "encoding/base64" + + pkgerrors "github.com/pkg/errors" + "log" +) + +// ConfigTemplate contains the parameters needed for ConfigTemplates +type ConfigTemplate struct { + TemplateName string `json:"template-name"` + Description string `json:"description"` + ChartName string +} + +// ConfigTemplateManager is an interface exposes the resource bundle ConfigTemplate functionality +type ConfigTemplateManager interface { + Create(rbName, rbVersion string, p ConfigTemplate) error + Get(rbName, rbVersion, templateName string) (ConfigTemplate, error) + Delete(rbName, rbVersion, templateName string) error + Upload(rbName, rbVersion, templateName string, inp []byte) error +} + +// ConfigTemplateKey is key struct +type ConfigTemplateKey struct { + RBName string `json:"rb-name"` + RBVersion string `json:"rb-version"` + TemplateName string `json:"template-name"` +} + +// We will use json marshalling to convert to string to +// preserve the underlying structure. +func (dk ConfigTemplateKey) String() string { + out, err := json.Marshal(dk) + if err != nil { + return "" + } + + return string(out) +} + +// ConfigTemplateClient implements the ConfigTemplateManager +// It will also be used to maintain some localized state +type ConfigTemplateClient struct { + storeName string + tagMeta string + tagContent string +} + +// NewConfigTemplateClient returns an instance of the ConfigTemplateClient +// which implements the ConfigTemplateManager +func NewConfigTemplateClient() *ConfigTemplateClient { + return &ConfigTemplateClient{ + storeName: "rbdef", + tagMeta: "metadata", + tagContent: "content", + } +} + +// Create an entry for the resource bundle ConfigTemplate in the database +func (v *ConfigTemplateClient) Create(rbName, rbVersion string, p ConfigTemplate) error { + + log.Printf("[ConfigiTemplate]: create %s", rbName) + // Name is required + if p.TemplateName == "" { + return pkgerrors.New("Name is required for Resource Bundle ConfigTemplate") + } + + //Check if ConfigTemplate already exists + _, err := v.Get(rbName, rbVersion, p.TemplateName) + if err == nil { + return pkgerrors.New(" ConfigTemplate already exists for this Definition") + } + + //Check if provided resource bundle information is valid + _, err = NewDefinitionClient().Get(rbName, rbVersion) + if err != nil { + return pkgerrors.Errorf("Invalid Resource Bundle ID provided: %s", err.Error()) + } + + key := ConfigTemplateKey{ + RBName: rbName, + RBVersion: rbVersion, + TemplateName: p.TemplateName, + } + + err = db.DBconn.Create(v.storeName, key, v.tagMeta, p) + if err != nil { + return pkgerrors.Wrap(err, "Creating ConfigTemplate DB Entry") + } + + return nil +} + +// Get returns the Resource Bundle ConfigTemplate for corresponding ID +func (v *ConfigTemplateClient) Get(rbName, rbVersion, templateName string) (ConfigTemplate, error) { + key := ConfigTemplateKey{ + RBName: rbName, + RBVersion: rbVersion, + TemplateName: templateName, + } + value, err := db.DBconn.Read(v.storeName, key, v.tagMeta) + if err != nil { + return ConfigTemplate{}, pkgerrors.Wrap(err, "Get ConfigTemplate") + } + + //value is a byte array + if value != nil { + template := ConfigTemplate{} + err = db.DBconn.Unmarshal(value, &template) + if err != nil { + return ConfigTemplate{}, pkgerrors.Wrap(err, "Unmarshaling ConfigTemplate Value") + } + return template, nil + } + + return ConfigTemplate{}, pkgerrors.New("Error getting ConfigTemplate") +} + +// Delete the Resource Bundle ConfigTemplate from database +func (v *ConfigTemplateClient) Delete(rbName, rbVersion, templateName string) error { + key := ConfigTemplateKey{ + RBName: rbName, + RBVersion: rbVersion, + TemplateName: templateName, + } + err := db.DBconn.Delete(v.storeName, key, v.tagMeta) + if err != nil { + return pkgerrors.Wrap(err, "Delete ConfigTemplate") + } + + err = db.DBconn.Delete(v.storeName, key, v.tagContent) + if err != nil { + return pkgerrors.Wrap(err, "Delete ConfigTemplate Content") + } + + return nil +} + +// Upload the contents of resource bundle into database +func (v *ConfigTemplateClient) Upload(rbName, rbVersion, templateName string, inp []byte) error { + + log.Printf("[ConfigTemplate]: Upload %s", templateName) + key := ConfigTemplateKey{ + RBName: rbName, + RBVersion: rbVersion, + TemplateName: templateName, + } + //ignore the returned data here. + t, err := v.Get(rbName, rbVersion, templateName) + if err != nil { + return pkgerrors.Errorf("Invalid ConfigTemplate Name provided %s", err.Error()) + } + + err = isTarGz(bytes.NewBuffer(inp)) + if err != nil { + return pkgerrors.Errorf("Error in file format %s", err.Error()) + } + + chartBasePath, err := ExtractTarBall(bytes.NewBuffer(inp)) + if err != nil { + return pkgerrors.Wrap(err, "Extracting Template") + } + + finfo, err := ioutil.ReadDir(chartBasePath) + if err != nil { + return pkgerrors.Wrap(err, "Detecting chart name") + } + + //Store the first directory with Chart.yaml found as the chart name + for _, f := range finfo { + if f.IsDir() { + //Check if Chart.yaml exists + if _, err = os.Stat(filepath.Join(chartBasePath, f.Name(), "Chart.yaml")); err == nil { + t.ChartName = f.Name() + break + } + } + } + if t.ChartName == "" { + return pkgerrors.New("Invalid template no Chart.yaml file found") + } + + err = db.DBconn.Create(v.storeName, key, v.tagMeta, t) + if err != nil { + return pkgerrors.Wrap(err, "Creating ConfigTemplate DB Entry") + } + + //Encode given byte stream to text for storage + encodedStr := base64.StdEncoding.EncodeToString(inp) + err = db.DBconn.Create(v.storeName, key, v.tagContent, encodedStr) + if err != nil { + return pkgerrors.Errorf("Error uploading data to db %s", err.Error()) + } + + return nil +} + +// Download the contents of the ConfigTemplate from DB +// Returns a byte array of the contents +func (v *ConfigTemplateClient) Download(rbName, rbVersion, templateName string) ([]byte, error) { + + log.Printf("[ConfigTemplate]: Download %s", templateName) + //ignore the returned data here + //Check if rb is valid + _, err := v.Get(rbName, rbVersion, templateName) + if err != nil { + return nil, pkgerrors.Errorf("Invalid ConfigTemplate Name provided: %s", err.Error()) + } + + key := ConfigTemplateKey{ + RBName: rbName, + RBVersion: rbVersion, + TemplateName: templateName, + } + value, err := db.DBconn.Read(v.storeName, key, v.tagContent) + if err != nil { + return nil, pkgerrors.Wrap(err, "Get Resource ConfigTemplate content") + } + + if value != nil { + //Decode the string from base64 + out, err := base64.StdEncoding.DecodeString(string(value)) + if err != nil { + return nil, pkgerrors.Wrap(err, "Decode base64 string") + } + + if out != nil && len(out) != 0 { + return out, nil + } + } + return nil, pkgerrors.New("Error downloading ConfigTemplate content") +} diff --git a/src/k8splugin/internal/rb/config_test.go b/src/k8splugin/internal/rb/config_test.go new file mode 100644 index 00000000..9bf97a51 --- /dev/null +++ b/src/k8splugin/internal/rb/config_test.go @@ -0,0 +1,259 @@ +/* + * Copyright 2018 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 rb + +import ( + "k8splugin/internal/db" + "reflect" + "strings" + "testing" + // pkgerrors "github.com/pkg/errors" +) + +func TestCreateConfig(t *testing.T) { + testCases := []struct { + label string + rbName string + rbVersion string + profileName string + inp Config + expectedError string + mockdb *db.MockEtcdClient + expected ConfigResult + }{ + { + label: "Create Config", + rbName: "testdef1", + rbVersion: "v1", + profileName: "testprofile1", + inp: Config{ + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + Values: map[string]interface{}{ + "values": "{\"namespace\": \"kafka\", \"topic\": {\"name\":\"orders\", \"cluster\":\"my-cluster\", \"partitions\": 10,\"replicas\": 2, }}"}, + }, + expected: ConfigResult{ + DefinitionName: "testdef1", + DefinitionVersion: "v1", + ProfileName: "testprofile1", + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + ConfigVersion: 1, + }, + expectedError: "", + mockdb: &db.MockEtcdClient{ + Items: nil, + Err: nil, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.Etcd = testCase.mockdb + resolve = func(rbName, rbVersion, profileName string, p Config) (configResourceList, error) { + return configResourceList{}, nil + } + impl := NewConfigClient() + got, err := impl.Create(testCase.rbName, testCase.rbVersion, testCase.profileName, testCase.inp) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Create returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("Create Resource Bundle returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestRollbackConfig(t *testing.T) { + testCases := []struct { + label string + rbName string + rbVersion string + profileName string + inp Config + inpUpdate1 Config + inpUpdate2 Config + expectedError string + mockdb *db.MockEtcdClient + expected1 ConfigResult + expected2 ConfigResult + expected3 ConfigResult + expected4 ConfigResult + rollbackConfig ConfigRollback + }{ + { + label: "Rollback Config", + rbName: "testdef1", + rbVersion: "v1", + profileName: "testprofile1", + inp: Config{ + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + Values: map[string]interface{}{ + "values": "{\"namespace\": \"kafka\", \"topic\": {\"name\":\"orders\", \"cluster\":\"my-cluster\", \"partitions\": 10,\"replicas\": 2, }}"}, + }, + inpUpdate1: Config{ + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + Values: map[string]interface{}{ + "values": "{\"namespace\": \"kafka\", \"topic\": {\"name\":\"orders\", \"cluster\":\"my-cluster\", \"partitions\": 20,\"replicas\": 2, }}"}, + }, + inpUpdate2: Config{ + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + Values: map[string]interface{}{ + "values": "{\"namespace\": \"kafka\", \"topic\": {\"name\":\"orders\", \"cluster\":\"my-cluster\", \"partitions\": 30,\"replicas\": 2, }}"}, + }, + expected1: ConfigResult{ + DefinitionName: "testdef1", + DefinitionVersion: "v1", + ProfileName: "testprofile1", + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + ConfigVersion: 1, + }, + expected2: ConfigResult{ + DefinitionName: "testdef1", + DefinitionVersion: "v1", + ProfileName: "testprofile1", + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + ConfigVersion: 2, + }, + expected3: ConfigResult{ + DefinitionName: "testdef1", + DefinitionVersion: "v1", + ProfileName: "testprofile1", + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + ConfigVersion: 3, + }, + expected4: ConfigResult{ + DefinitionName: "testdef1", + DefinitionVersion: "v1", + ProfileName: "testprofile1", + ConfigName: "testconfig1", + TemplateName: "testtemplate1", + ConfigVersion: 4, + }, + expectedError: "", + mockdb: &db.MockEtcdClient{ + Items: nil, + Err: nil, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.Etcd = testCase.mockdb + resolve = func(rbName, rbVersion, profileName string, p Config) (configResourceList, error) { + return configResourceList{}, nil + } + impl := NewConfigClient() + got, err := impl.Create(testCase.rbName, testCase.rbVersion, testCase.profileName, testCase.inp) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Create returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected1, got) == false { + t.Errorf("Create Resource Bundle returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected1) + } + } + got, err = impl.Update(testCase.rbName, testCase.rbVersion, testCase.profileName, testCase.inp.ConfigName, testCase.inpUpdate1) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Create returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected2, got) == false { + t.Errorf("Create Resource Bundle returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected2) + } + } + got, err = impl.Update(testCase.rbName, testCase.rbVersion, testCase.profileName, testCase.inp.ConfigName, testCase.inpUpdate2) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Create returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected3, got) == false { + t.Errorf("Create Resource Bundle returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected3) + } + } + got, err = impl.Delete(testCase.rbName, testCase.rbVersion, testCase.profileName, testCase.inp.ConfigName) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Create returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected4, got) == false { + t.Errorf("Create Resource Bundle returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected4) + } + } + testCase.rollbackConfig.AnyOf.ConfigVersion = "2" + err = impl.Rollback(testCase.rbName, testCase.rbVersion, testCase.profileName, testCase.rollbackConfig) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Create returned an unexpected error %s", err) + } + } + rollbackConfig, err := impl.Get(testCase.rbName, testCase.rbVersion, testCase.profileName, testCase.inp.ConfigName) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Create returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.inpUpdate1, rollbackConfig) == false { + t.Errorf("Rollback config failed: got %v;"+ + " expected %v", rollbackConfig, testCase.inpUpdate1) + } + } + }) + } +} diff --git a/src/k8splugin/internal/utils.go b/src/k8splugin/internal/utils.go index 4b28b688..3b08dd26 100644 --- a/src/k8splugin/internal/utils.go +++ b/src/k8splugin/internal/utils.go @@ -90,6 +90,17 @@ func CheckDatabaseConnection() error { if err != nil { return pkgerrors.Cause(err) } + // TODO Convert these to configuration files instead of environment variables. + c := db.EtcdConfig{ + Endpoint: os.Getenv("ETCD_ENDPOINT_IP"), + CertFile: os.Getenv("ETCD_CERT_FILE"), + KeyFile: os.Getenv("ETCD_KEY_FILE"), + CAFile: os.Getenv("ETCD_TRUSTED_CA_FILE"), + } + err = db.NewEtcdClient(nil, c) + if err != nil { + log.Printf("Etcd Client Initialization failed") + } return nil } -- cgit 1.2.3-korg