Day 2 Configuration API's 55/84755/4
authorrsood <ritu.sood@intel.com>
Wed, 10 Apr 2019 05:00:04 +0000 (05:00 +0000)
committerrsood <ritu.sood@intel.com>
Thu, 11 Apr 2019 04:53:43 +0000 (04:53 +0000)
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 <ritu.sood@intel.com>
16 files changed:
src/k8splugin/api/api.go
src/k8splugin/api/confighandler.go [new file with mode: 0644]
src/k8splugin/api/configtemplatehandler.go [new file with mode: 0644]
src/k8splugin/api/defhandler_test.go
src/k8splugin/api/instancehandler_test.go
src/k8splugin/api/profilehandler_test.go
src/k8splugin/cmd/main.go
src/k8splugin/go.mod
src/k8splugin/go.sum
src/k8splugin/internal/db/etcd.go [new file with mode: 0644]
src/k8splugin/internal/db/etcd_testing.go [new file with mode: 0644]
src/k8splugin/internal/rb/config.go [new file with mode: 0644]
src/k8splugin/internal/rb/config_backend.go [new file with mode: 0644]
src/k8splugin/internal/rb/config_template.go [new file with mode: 0644]
src/k8splugin/internal/rb/config_test.go [new file with mode: 0644]
src/k8splugin/internal/utils.go

index 54147d2..2003a80 100644 (file)
@@ -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 (file)
index 0000000..93098d6
--- /dev/null
@@ -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 (file)
index 0000000..a91165c
--- /dev/null
@@ -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)
+}
index 03189e1..321eb46 100644 (file)
@@ -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 {
index d01d5df..ed7135a 100644 (file)
@@ -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)
index 932a024..2473fea 100644 (file)
@@ -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 {
index e600c0d..96e1c8e 100644 (file)
@@ -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")
 
index 29a10ec..59c7a17 100644 (file)
@@ -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
index f22fe8d..453a45d 100644 (file)
@@ -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 (file)
index 0000000..fda44b2
--- /dev/null
@@ -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 (file)
index 0000000..12b17e3
--- /dev/null
@@ -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 (file)
index 0000000..3bd8347
--- /dev/null
@@ -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 (file)
index 0000000..b61fc49
--- /dev/null
@@ -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 (file)
index 0000000..cdb1b90
--- /dev/null
@@ -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 (file)
index 0000000..9bf97a5
--- /dev/null
@@ -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)
+                               }
+                       }
+               })
+       }
+}
index 4b28b68..3b08dd2 100644 (file)
@@ -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
 }