From 67dd59385cf983ef1307e3b3e410a8f773d8a5c3 Mon Sep 17 00:00:00 2001 From: Shashank Kumar Shankar Date: Wed, 28 Feb 2018 17:54:59 -0800 Subject: Add feature to hold configs in filesystem This patch adds feature to hold config files on filesystem and adds all unit tests to have enough coverage for milestones. Change-Id: Icd6f3dc93e0f419500f82f0a6ccd62e500dfc918 Issue-ID: MUSIC-42 Signed-off-by: Shashank Kumar Shankar --- src/dkv/api/backendConsulConnection.go | 133 +++++++++++++ src/dkv/api/backendFilesystemConnection.go | 200 +++++++++++++++++++ src/dkv/api/backendPropertiesConnection.go | 146 ++++++++++++++ src/dkv/api/backendfakes.go | 162 +++++++++++++++ src/dkv/api/configHandlers.go | 177 +++++++++++++++++ src/dkv/api/configHandlers_test.go | 234 ++++++++++++++++++++++ src/dkv/api/consulConnection.go | 130 ------------ src/dkv/api/consulConnection_test.go | 17 -- src/dkv/api/endpointViews.go | 159 --------------- src/dkv/api/endpointViews_fake.go | 96 --------- src/dkv/api/endpointViews_test.go | 214 -------------------- src/dkv/api/initialise.go | 10 +- src/dkv/api/propertiesReader.go | 114 ----------- src/dkv/api/propertiesReader_test.go | 19 -- src/dkv/api/queryConsulHandlers.go | 87 ++++++++ src/dkv/api/queryConsulHandlers_test.go | 105 ++++++++++ src/dkv/api/registrationHandlers.go | 173 ++++++++++++++++ src/dkv/api/registrationHandlers_test.go | 276 ++++++++++++++++++++++++++ src/dkv/api/token_service_map.json | 1 + src/dkv/api/utils.go | 153 +++++++++++++++ src/dkv/api/utils_test.go | 306 +++++++++++++++++++++++++++++ 21 files changed, 2154 insertions(+), 758 deletions(-) create mode 100644 src/dkv/api/backendConsulConnection.go create mode 100644 src/dkv/api/backendFilesystemConnection.go create mode 100644 src/dkv/api/backendPropertiesConnection.go create mode 100644 src/dkv/api/backendfakes.go create mode 100644 src/dkv/api/configHandlers.go create mode 100644 src/dkv/api/configHandlers_test.go delete mode 100644 src/dkv/api/consulConnection.go delete mode 100644 src/dkv/api/consulConnection_test.go delete mode 100644 src/dkv/api/endpointViews.go delete mode 100644 src/dkv/api/endpointViews_fake.go delete mode 100644 src/dkv/api/endpointViews_test.go delete mode 100644 src/dkv/api/propertiesReader.go delete mode 100644 src/dkv/api/propertiesReader_test.go create mode 100644 src/dkv/api/queryConsulHandlers.go create mode 100644 src/dkv/api/queryConsulHandlers_test.go create mode 100644 src/dkv/api/registrationHandlers.go create mode 100644 src/dkv/api/registrationHandlers_test.go create mode 100644 src/dkv/api/token_service_map.json create mode 100644 src/dkv/api/utils.go create mode 100644 src/dkv/api/utils_test.go (limited to 'src/dkv/api') diff --git a/src/dkv/api/backendConsulConnection.go b/src/dkv/api/backendConsulConnection.go new file mode 100644 index 0000000..9c2f8d6 --- /dev/null +++ b/src/dkv/api/backendConsulConnection.go @@ -0,0 +1,133 @@ +/* + * 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 ( + "errors" + consulapi "github.com/hashicorp/consul/api" + "os" +) + +// Interface to have all signature methods. +type ConsulRequester interface { + InitializeConsulClient() error + CheckConsulHealth() error + RequestPUT(string, string) error + RequestGET(string) (string, error) + RequestGETS() ([]string, error) + RequestDELETE(string) error +} + +type ConsulStruct struct { + consulClient *consulapi.Client +} + +/* +This var is an interface used to initialize ConsulStruct when the who API is brought up. This is done this way so +that a fake Consul can be created which satisfies the interface and we can use that fake Consul in unit testing. +*/ +var Consul ConsulRequester + +/* +The following functions seems like they are not used. But since they are following the ConsulRequest interface, +they can be visible to any Struct which is initiated using the ConsulRequest. This is done for this project in +the initialise.go file where we are creating a ConsulStruct and assigning it to Consul var which is declared +above. +*/ +func (c *ConsulStruct) InitializeConsulClient() error { + if os.Getenv("CONSUL_IP") == "" { + return errors.New("CONSUL_IP environment variable not set.") + } + config := consulapi.DefaultConfig() + config.Address = os.Getenv("CONSUL_IP") + ":8500" + + client, err := consulapi.NewClient(config) + if err != nil { + return err + } + c.consulClient = client + + return nil +} + +func (c *ConsulStruct) CheckConsulHealth() error { + kv := c.consulClient.KV() + _, _, err := kv.Get("test", nil) + if err != nil { + return errors.New("[ERROR] Cannot talk to Consul. Check if it is running/reachable.") + } + return nil +} + +func (c *ConsulStruct) RequestPUT(key string, value string) error { + + kv := c.consulClient.KV() + + p := &consulapi.KVPair{Key: key, Value: []byte(value)} + + _, err := kv.Put(p, nil) + + if err != nil { + return err + } + + return nil +} + +func (c *ConsulStruct) RequestGET(key string) (string, error) { + + kv := c.consulClient.KV() + + pair, _, err := kv.Get(key, nil) + + if pair == nil { + return string("No value found for key."), err + } + return string(pair.Value), err + +} + +func (c *ConsulStruct) RequestGETS() ([]string, error) { + + kv := c.consulClient.KV() + + pairs, _, err := kv.List("", nil) + + if len(pairs) == 0 { + return []string{"No keys found."}, err + } + + var res []string + + for _, keypair := range pairs { + res = append(res, keypair.Key) + } + + return res, err +} + +func (c *ConsulStruct) RequestDELETE(key string) error { + kv := c.consulClient.KV() + + _, err := kv.Delete(key, nil) + + if err != nil { + return err + } + + return nil +} diff --git a/src/dkv/api/backendFilesystemConnection.go b/src/dkv/api/backendFilesystemConnection.go new file mode 100644 index 0000000..f09e74f --- /dev/null +++ b/src/dkv/api/backendFilesystemConnection.go @@ -0,0 +1,200 @@ +/* + * 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 ( + "errors" + uuid "github.com/hashicorp/go-uuid" + "net/http" + "os" +) + +type DirectoryOperationer interface { + // Service Operations. + CreateService(CreateRegisterServiceBody) (string, error) + RemoveService(string) error + CreateServiceSubdomain(string, string) error + RemoveServiceSubdomain(string, string) error + // Directory Operations. + CreateDirectory(string) error + RemoveDirectory(string) error + RemoveSubDirectory(string, string) error + RemoveFile(string, string, string) error + FindService(string) (string, bool, error) + FetchFile(http.ResponseWriter, *http.Request, string, string, string) + CreateFile(string) (*os.File, error) +} + +type DirectoryStruct struct { + directory string +} + +const ( + MOUNTPATH = "../../mountpath/" + JSONPATH = "api/token_service_map.json" +) + +var Directory DirectoryOperationer + +func (d *DirectoryStruct) CreateService(body CreateRegisterServiceBody) (string, error) { + + // Having same name is prohibited? + found, err := FindServiceInJSON(JSONPATH, body.Domain) + if err != nil { + return "", err + } + if found { + return "", errors.New("Service already found. Check name.") + } + + token, err := uuid.GenerateUUID() + if err != nil { + return "", err + } + + err = d.CreateDirectory(token) + if err != nil { + return "", err + } + + err = WriteJSON(JSONPATH, token, body.Domain) + + if err != nil { + return "", err + } + return token, nil +} + +func (d *DirectoryStruct) CreateServiceSubdomain(token string, subdomain string) error { + foundToken, err := FindTokenInJSON(JSONPATH, token) + if err != nil { + return err + } + if foundToken == false { + return errors.New("Token not found. Please check token or if service is created.") + } + err = d.CreateSubDirectory(token, subdomain) + if err != nil { + return err + } + return nil +} + +func (d *DirectoryStruct) RemoveService(token string) error { + err := DeleteInJSON(JSONPATH, token) + if err != nil { + return err + } + err = d.RemoveDirectory(token) + if err != nil { + return err + } + return nil +} + +func (d *DirectoryStruct) FindService(token string) (string, bool, error) { + service, found, err := GetServicebyToken(JSONPATH, token) + if err != nil { + return "", false, err + } + return service, found, nil +} + +func (d *DirectoryStruct) RemoveServiceSubdomain(token string, subdomain string) error { + foundToken, err := FindTokenInJSON(JSONPATH, token) + if err != nil { + return err + } + if foundToken == false { + return errors.New("Token not found. Please check token or if service is created.") + } + err = d.RemoveSubDirectory(token, subdomain) + if err != nil { + return err + } + return nil +} + +func (d *DirectoryStruct) CreateDirectory(token string) error { + // Permissions inside mount point? + err := os.Mkdir(MOUNTPATH+token, os.FileMode(0770)) + if err != nil { + return err + } + return nil +} + +func (d *DirectoryStruct) CreateSubDirectory(token string, subdomain string) error { + err := os.Mkdir(MOUNTPATH+token+"/"+subdomain, os.FileMode(0770)) + if err != nil { + return err + } + return nil +} + +func (d *DirectoryStruct) RemoveDirectory(token string) error { + err := os.RemoveAll(MOUNTPATH + token) + if err != nil { + return err + } + return nil +} + +func (d *DirectoryStruct) RemoveSubDirectory(token string, subdomain string) error { + err := os.RemoveAll(MOUNTPATH + token + "/" + subdomain) + if err != nil { + return err + } + return nil +} + +func (d *DirectoryStruct) RemoveFile(token string, subdomain string, filename string) error { + var filepath = "" + if subdomain != "" { + filepath += MOUNTPATH + token + "/" + subdomain + "/" + filename + } else { + filepath += MOUNTPATH + token + "/" + filename + } + // If error, it seems to show the mounthpath back to the client. This is not good + // error return practise. It shoudn't return the exact file path on the system. + err := os.Remove(filepath) + if err != nil { + return err + } + return nil +} + +func (d *DirectoryStruct) FetchFile( + w http.ResponseWriter, r *http.Request, token string, subdomain string, filename string) { + + var filepath = "" + if subdomain != "" { + filepath += MOUNTPATH + token + "/" + subdomain + "/" + filename + } else { + filepath += MOUNTPATH + token + "/" + filename + } + + http.ServeFile(w, r, filepath) +} + +func (d *DirectoryStruct) CreateFile(filepath string) (*os.File, error) { + f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0770) + if err != nil { + return nil, err + } + return f, nil +} diff --git a/src/dkv/api/backendPropertiesConnection.go b/src/dkv/api/backendPropertiesConnection.go new file mode 100644 index 0000000..24140cd --- /dev/null +++ b/src/dkv/api/backendPropertiesConnection.go @@ -0,0 +1,146 @@ +/* + * 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 ( + "errors" + "github.com/magiconair/properties" + "io/ioutil" + "log" + "os" + "sync" +) + +type KeyValuesInterface interface { + WriteKVsToConsul(string, string) error + ConfigReader(string, string, string) error + ReadMultiplePropertiesRecursive(string) error + ReadMultipleProperties(string) error + ReadProperty(string) error +} + +type KeyValuesStruct struct { + sync.RWMutex + kvs map[string]string +} + +var KeyValues KeyValuesInterface + +func (kvStruct *KeyValuesStruct) WriteKVsToConsul(token string, subdomain string) error { + var prefix = "" + if subdomain != "" { + prefix += token + "/" + subdomain + } else { + prefix += token + "/" + } + for key, value := range kvStruct.kvs { + key = prefix + key + err := Consul.RequestPUT(key, value) + if err != nil { + return err + } + log.Println("[INFO] Key: ", key, "| Value: ", value) + } + log.Println("[INFO] Wrote KVs to Consul.") + return nil +} + +func (kvStruct *KeyValuesStruct) ConfigReader(token string, subdomain string, filename string) error { + defer kvStruct.Unlock() + + kvStruct.Lock() + var filepath = MOUNTPATH + + if filename != "" && subdomain != "" { + // Specific file in specific domain. + filepath += token + "/" + subdomain + "/" + filename + err := kvStruct.ReadProperty(filepath) + if err != nil { + return err + } + return nil + } + + if filename != "" && subdomain == "" { + // Specific file in Token + filepath += token + "/" + filename + err := kvStruct.ReadProperty(filepath) + if err != nil { + return err + } + return nil + } + + if filename == "" && subdomain != "" { + // All files in specific domain + filepath += token + "/" + subdomain + err := kvStruct.ReadMultipleProperties(filepath) + if err != nil { + return err + } + } + + filepath += token + err := kvStruct.ReadMultiplePropertiesRecursive(filepath) + if err != nil { + return err + } + return nil +} + +func (kvStruct *KeyValuesStruct) ReadMultiplePropertiesRecursive(path string) error { + // Go inside each sub directory and run ReadMultipleProperties inside. + files, err := ioutil.ReadDir(path) + if err != nil { + return err + } + + for _, f := range files { + fi, _ := os.Stat(path + "/" + f.Name()) + if fi.Mode().IsDir() { + kvStruct.ReadMultipleProperties(path + "/" + f.Name()) + } else { + kvStruct.ReadProperty(path + "/" + f.Name()) + } + } + return nil +} + +func (kvStruct *KeyValuesStruct) ReadMultipleProperties(path string) error { + files, err := ioutil.ReadDir(path) + if err != nil { + return err + } + + for _, f := range files { + kvStruct.ReadProperty(path + f.Name()) + } + + return nil +} + +func (kvStruct *KeyValuesStruct) ReadProperty(path string) error { + _, err := os.Stat(path) + if err != nil { + return errors.New("File does not exists.") + } + p := properties.MustLoadFile(path, properties.UTF8) + for _, key := range p.Keys() { + kvStruct.kvs[key] = p.MustGet(key) + } + return nil +} diff --git a/src/dkv/api/backendfakes.go b/src/dkv/api/backendfakes.go new file mode 100644 index 0000000..5415608 --- /dev/null +++ b/src/dkv/api/backendfakes.go @@ -0,0 +1,162 @@ +/* + * 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 ( + "errors" + "net/http" +) + +/* +A ConsulStruct is added inside this so that FakeConsul becomes an implementation of the Consul interface. +If we don't add ConsulStruct inside this, it complains that the FakeConsul Struct doesn't implement all the methods +defined in Consul interface. +*/ +// Correct +type FakeConsul struct { + ConsulStruct +} + +func (f *FakeConsul) RequestGETS() ([]string, error) { + return []string{"key1", "key2"}, nil +} + +func (f *FakeConsul) RequestGET(key string) (string, error) { + return key, nil +} + +func (f *FakeConsul) RequestPUT(key string, value string) error { + return nil +} + +func (f *FakeConsul) RequestDELETE(key string) error { + return nil +} + +// Error +type FakeConsulErr struct { + ConsulStruct +} + +func (f *FakeConsulErr) RequestGETS() ([]string, error) { + return []string{"", ""}, errors.New("Internal Server Error") +} + +func (f *FakeConsulErr) RequestGET(key string) (string, error) { + return "", errors.New("Internal Server Error") +} + +func (f *FakeConsulErr) RequestDELETE(key string) error { + return errors.New("Internal Server Error") +} + +/* +This is done similar to the fake Consul above to pass FakeKeyValues to the interface and control method's outputs +as required. +*/ +//Correct +type FakeKeyValues struct { + KeyValuesStruct +} + +func (f *FakeKeyValues) ConfigReader(token string, subdomain string, filename string) error { + return nil +} + +func (f *FakeKeyValues) WriteKVsToConsul(token string, subdomain string) error { + return nil +} + +// Error +type FakeKeyValuesErr struct { + KeyValuesStruct +} + +func (f *FakeKeyValuesErr) ConfigReader(token string, subdomain string, filename string) error { + return errors.New("Internal Server Error") +} + +func (f *FakeKeyValuesErr) WriteKVsToConsul(token string, subdomain string) error { + return errors.New("Internal Server Error") +} + +// Correct +type FakeDirectory struct { + DirectoryStruct +} + +func (f *FakeDirectory) CreateService(CreateRegisterServiceBody) (string, error) { + return "", nil +} + +func (f *FakeDirectory) RemoveService(token string) error { + return nil +} + +func (f *FakeDirectory) CreateServiceSubdomain(token string, subdomain string) error { + return nil +} + +func (f *FakeDirectory) RemoveServiceSubdomain(token string, subdomain string) error { + return nil +} + +func (f *FakeDirectory) FindService(token string) (string, bool, error) { + return "service1", true, nil +} + +func (f *FakeDirectory) FetchFile( + w http.ResponseWriter, r *http.Request, token string, subdomain string, filename string) { +} + +func (f *FakeDirectory) RemoveFile(token string, subdomain string, filename string) error { + return nil +} + +// Error +type FakeDirectoryErr struct { + DirectoryStruct +} + +func (f *FakeDirectoryErr) CreateService(CreateRegisterServiceBody) (string, error) { + return "", errors.New("Internal Server Error.") +} + +func (f *FakeDirectoryErr) RemoveService(token string) error { + return errors.New("Internal Server Error.") +} + +func (f *FakeDirectoryErr) CreateServiceSubdomain(token string, subdomain string) error { + return errors.New("Internal Server Error.") +} + +func (f *FakeDirectoryErr) RemoveServiceSubdomain(token string, subdomain string) error { + return errors.New("Internal Server Error.") +} + +func (f *FakeDirectoryErr) FindService(token string) (string, bool, error) { + return "", false, errors.New("Internal Server Error.") +} + +func (f *FakeDirectoryErr) FetchFile( + w http.ResponseWriter, r *http.Request, token string, subdomain string, filename string) { + +} + +func (f *FakeDirectoryErr) RemoveFile(token string, subdomain string, filename string) error { + return errors.New("Internal Server Error.") +} diff --git a/src/dkv/api/configHandlers.go b/src/dkv/api/configHandlers.go new file mode 100644 index 0000000..f5bac34 --- /dev/null +++ b/src/dkv/api/configHandlers.go @@ -0,0 +1,177 @@ +/* + * 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" + "errors" + "github.com/gorilla/mux" + "io" + "mime/multipart" + "net/http" +) + +type UploadConfigBody struct { + Token string + File multipart.File + Subdomain string +} + +type LoadConfigBody struct { + Token string `json:"token"` + Filename string `json:"filename"` + Subdomain string `json:"subdomain"` +} + +func ValidateLoadConfigBody(body LoadConfigBody) error { + if body.Token == "" { + return errors.New("Token not set. Please set Token in POST.") + } + return nil +} + +func HandleConfigUpload(w http.ResponseWriter, r *http.Request) { + r.ParseMultipartForm(100000) // 2k bytes? + file, handler, err := r.FormFile("configFile") + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, "Error in uploaded file.") + return + } + defer file.Close() + + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + return + } + + token := r.Form.Get("token") + subdomain := r.Form.Get("subdomain") + + if token == "" { + GenerateResponse(w, r, http.StatusBadRequest, "Token not present in Form data.") + return + } + + var filename = "" + if subdomain != "" { + filename += token + "/" + subdomain + "/" + handler.Filename + } else { + filename += token + "/" + handler.Filename + } + + f, err := Directory.CreateFile(MOUNTPATH + filename) + + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + return + } + defer f.Close() + io.Copy(f, file) +} + +func HandleConfigLoad(w http.ResponseWriter, r *http.Request) { + + var body LoadConfigBody + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&body) + + if err != nil { + GenerateResponse(w, r, http.StatusBadRequest, "Empty body.") + return + } + + err = ValidateLoadConfigBody(body) + + if err != nil { + GenerateResponse(w, r, http.StatusBadRequest, string(err.Error())) + return + } + + err = KeyValues.ConfigReader(body.Token, body.Subdomain, body.Filename) + + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + return + } + + err = KeyValues.WriteKVsToConsul(body.Token, body.Subdomain) + + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + } else { + GenerateResponse(w, r, http.StatusOK, "Configuration read and Key Values loaded to Consul.") + } +} + +func HandleDefaultConfigLoad(w http.ResponseWriter, r *http.Request) { + err := KeyValues.ConfigReader("default", "", "") + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + return + } + err = KeyValues.WriteKVsToConsul("default", "") + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + } else { + GenerateResponse(w, r, http.StatusOK, "Default Configuration read and default Key Values loaded to Consul.") + } +} + +func HandleConfigGet(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + token := vars["token"] + filename := vars["filename"] + subdomain := vars["subdomain"] + + if token == "" { + GenerateResponse(w, r, http.StatusBadRequest, "Token not passed.") + return + } + + if filename == "" { + GenerateResponse(w, r, http.StatusBadRequest, "filename not passed.") + return + } + + Directory.FetchFile(w, r, token, subdomain, filename) +} + +func HandleConfigDelete(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + token := vars["token"] + filename := vars["filename"] + subdomain := vars["subdomain"] + + if token == "" { + GenerateResponse(w, r, http.StatusBadRequest, "Token not passed.") + return + } + + if filename == "" { + GenerateResponse(w, r, http.StatusBadRequest, "filename not passed.") + return + } + + err := Directory.RemoveFile(token, subdomain, filename) + + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + } else { + GenerateResponse(w, r, http.StatusOK, "Deletion of config is successful.") + } +} diff --git a/src/dkv/api/configHandlers_test.go b/src/dkv/api/configHandlers_test.go new file mode 100644 index 0000000..e2f796a --- /dev/null +++ b/src/dkv/api/configHandlers_test.go @@ -0,0 +1,234 @@ +/* + * 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 ( + "bytes" + "encoding/json" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +func RouterConfig() *mux.Router { + router := mux.NewRouter() + router.HandleFunc("/v1/config", HandleConfigUpload).Methods("POST") + router.HandleFunc("/v1/config/{token}/{filename}", HandleConfigGet).Methods("GET") + router.HandleFunc("/v1/config/{token}/{filename}", HandleConfigDelete).Methods("DELETE") + router.HandleFunc("/v1/config/load", HandleConfigLoad).Methods("POST") + router.HandleFunc("/v1/config/load-default", HandleDefaultConfigLoad).Methods("GET") + return router +} + +func TestHandleConfigGet(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("GET", "/v1/config/token1/filename1", nil) + response := httptest.NewRecorder() + RouterConfig().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} +func TestHandleConfigDelete(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("DELETE", "/v1/config/token1/filename1", nil) + response := httptest.NewRecorder() + RouterConfig().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleConfigDelete_err(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectoryErr{} + + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("DELETE", "/v1/config/token1/filename1", nil) + response := httptest.NewRecorder() + RouterConfig().ServeHTTP(response, request) + + assert.Equal(t, 500, response.Code, "500 response is expected") +} + +func TestHandleConfigPOST(t *testing.T) { + oldConsul := Consul + oldKeyValues := KeyValues + + Consul = &FakeConsul{} + KeyValues = &FakeKeyValues{} + + defer func() { + Consul = oldConsul + KeyValues = oldKeyValues + }() + + body := &LoadConfigBody{ + Token: "test", + Filename: "test", + Subdomain: "test", + } + + b, _ := json.Marshal(body) + + // json Marshal converts struct to json in Bytes. But bytes doesn't have + // io reader needed. So the byte is passed to NewBuffer. + request, _ := http.NewRequest("POST", "/v1/config/load", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterConfig().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleConfigPOST_only_token(t *testing.T) { + oldConsul := Consul + oldKeyValues := KeyValues + + Consul = &FakeConsul{} + KeyValues = &FakeKeyValues{} + + defer func() { + Consul = oldConsul + KeyValues = oldKeyValues + }() + + body := &LoadConfigBody{ + Token: "test", + Filename: "", + Subdomain: "", + } + + b, _ := json.Marshal(body) + + // json Marshal converts struct to json in Bytes. But bytes doesn't have + // io reader needed. So the byte is passed to NewBuffer. + request, _ := http.NewRequest("POST", "/v1/config/load", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterConfig().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleConfigPOST_no_body(t *testing.T) { + oldConsul := Consul + oldKeyValues := KeyValues + + Consul = &FakeConsul{} + KeyValues = &FakeKeyValues{} + + defer func() { + Consul = oldConsul + KeyValues = oldKeyValues + }() + + body := &LoadConfigBody{} + + b, _ := json.Marshal(body) + + request, _ := http.NewRequest("POST", "/v1/config/load", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterConfig().ServeHTTP(response, request) + + assert.Equal(t, 400, response.Code, "400 response is expected") +} + +func TestHandleConfigPOST_ConsulError(t *testing.T) { + oldConsul := Consul + oldKeyValues := KeyValues + + Consul = &FakeConsulErr{} + KeyValues = &FakeKeyValuesErr{} + + defer func() { + Consul = oldConsul + KeyValues = oldKeyValues + }() + + body := &LoadConfigBody{ + Token: "test", + Filename: "test", + Subdomain: "test", + } + + b, _ := json.Marshal(body) + + request, _ := http.NewRequest("POST", "/v1/config/load", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterConfig().ServeHTTP(response, request) + + assert.Equal(t, 500, response.Code, "500 response is expected") +} + +func TestHandleConfigUpload_err(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("POST", "/v1/config", nil) + response := httptest.NewRecorder() + RouterConfig().ServeHTTP(response, request) + + assert.Equal(t, 500, response.Code, "500 response is expected") +} + +func TestHandleDefaultConfigLoad(t *testing.T) { + oldConsul := Consul + oldKeyValues := KeyValues + + Consul = &FakeConsul{} + KeyValues = &FakeKeyValues{} + + defer func() { + Consul = oldConsul + KeyValues = oldKeyValues + }() + + request, _ := http.NewRequest("GET", "/v1/config/load-default", nil) + response := httptest.NewRecorder() + RouterConfig().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleDefaultConfigLoad_err(t *testing.T) { + oldConsul := Consul + oldKeyValues := KeyValues + + Consul = &FakeConsul{} + KeyValues = &FakeKeyValuesErr{} + + defer func() { + Consul = oldConsul + KeyValues = oldKeyValues + }() + + request, _ := http.NewRequest("GET", "/v1/config/load-default", nil) + response := httptest.NewRecorder() + RouterConfig().ServeHTTP(response, request) + + assert.Equal(t, 500, response.Code, "500 response is expected") +} diff --git a/src/dkv/api/consulConnection.go b/src/dkv/api/consulConnection.go deleted file mode 100644 index 5ea79fd..0000000 --- a/src/dkv/api/consulConnection.go +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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 ( - "errors" - consulapi "github.com/hashicorp/consul/api" - "os" -) - -// Interface to have all signature methods. -type ConsulRequester interface { - InitializeConsulClient() error - CheckConsulHealth() error - RequestPUT(string, string) error - RequestGET(string) (string, error) - RequestGETS() ([]string, error) - RequestDELETE(string) error -} - -type ConsulStruct struct { - consulClient *consulapi.Client -} - -/* -This var is an interface used to initialize ConsulStruct when the who API is brought up. This is done this way so -that a fake Consul can be created which satisfies the interface and we can use that fake Consul in unit testing. -*/ -var Consul ConsulRequester - -/* -The following functions seems like they are not used. But since they are following the ConsulRequest interface, -they can be visible to any Struct which is initiated using the ConsulRequest. This is done for this project in -the initialise.go file where we are creating a ConsulStruct and assigning it to Consul var which is declared -above. -*/ -func (c *ConsulStruct) InitializeConsulClient() error { - config := consulapi.DefaultConfig() - config.Address = os.Getenv("CONSUL_IP") + ":8500" - - client, err := consulapi.NewClient(config) - if err != nil { - return err - } - c.consulClient = client - - return nil -} - -func (c *ConsulStruct) CheckConsulHealth() error { - kv := c.consulClient.KV() - _, _, err := kv.Get("test", nil) - if err != nil { - return errors.New("[ERROR] Cannot talk to Consul. Check if it is running/reachable.") - } - return nil -} - -func (c *ConsulStruct) RequestPUT(key string, value string) error { - - kv := c.consulClient.KV() - - p := &consulapi.KVPair{Key: key, Value: []byte(value)} - - _, err := kv.Put(p, nil) - - if err != nil { - return err - } - - return nil -} - -func (c *ConsulStruct) RequestGET(key string) (string, error) { - - kv := c.consulClient.KV() - - pair, _, err := kv.Get(key, nil) - - if pair == nil { - return string("No value found for key."), err - } - return string(pair.Value), err - -} - -func (c *ConsulStruct) RequestGETS() ([]string, error) { - - kv := c.consulClient.KV() - - pairs, _, err := kv.List("", nil) - - if len(pairs) == 0 { - return []string{"No keys found."}, err - } - - var res []string - - for _, keypair := range pairs { - res = append(res, keypair.Key) - } - - return res, err -} - -func (c *ConsulStruct) RequestDELETE(key string) error { - kv := c.consulClient.KV() - - _, err := kv.Delete(key, nil) - - if err != nil { - return err - } - - return nil -} diff --git a/src/dkv/api/consulConnection_test.go b/src/dkv/api/consulConnection_test.go deleted file mode 100644 index cc973ce..0000000 --- a/src/dkv/api/consulConnection_test.go +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 diff --git a/src/dkv/api/endpointViews.go b/src/dkv/api/endpointViews.go deleted file mode 100644 index 8f77061..0000000 --- a/src/dkv/api/endpointViews.go +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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" - "errors" - "github.com/gorilla/mux" - "net/http" -) - -type ResponseStringStruct struct { - Response string `json:"response"` -} - -type ResponseGETStruct struct { - Response map[string]string `json:"response"` -} - -type ResponseGETSStruct struct { - Response []string `json:"response"` -} - -type POSTBodyStruct struct { - Domain string `json:"domain"` - Type *TypeStruct `json:"type"` -} - -type TypeStruct struct { - FilePath string `json:"file_path"` -} - -func ValidateBody(body POSTBodyStruct) error { - if body.Domain == "" { - return errors.New("Domain not set. Please set domain in POST.") - } - if body.Type == nil { - return errors.New("Type not set. Recheck POST data.") - } else if body.Type.FilePath == "" { - return errors.New("file_path not set") - } else { - return nil - } -} - -func HandlePOST(w http.ResponseWriter, r *http.Request) { - - var body POSTBodyStruct - - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&body) - - if err != nil { - req := ResponseStringStruct{Response: "Empty body."} - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(&req) - return - } - - err = ValidateBody(body) - - if err != nil { - req := ResponseStringStruct{Response: string(err.Error())} - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(req) - return - } - - err = KeyValues.ReadConfigs(body) - - if err != nil { - req := ResponseStringStruct{Response: string(err.Error())} - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(req) - return - } - - err = KeyValues.WriteKVsToConsul(body.Domain) - - if err != nil { - req := ResponseStringStruct{Response: string(err.Error())} - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(req) - } else { - req := ResponseStringStruct{Response: "Configuration read and default Key Values loaded to Consul"} - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(&req) - } -} - -func HandleGET(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - key := vars["key"] - - value, err := Consul.RequestGET(key) - - if err != nil { - req := ResponseStringStruct{Response: string(err.Error())} - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(req) - } else { - req := ResponseGETStruct{Response: map[string]string{key: value}} - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(req) - } -} - -func HandleGETS(w http.ResponseWriter, r *http.Request) { - - values, err := Consul.RequestGETS() - - if err != nil { - req := ResponseStringStruct{Response: string(err.Error())} - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(req) - } else { - req := ResponseGETSStruct{Response: values} - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(req) - } -} - -func HandleDELETE(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - key := vars["key"] - - err := Consul.RequestDELETE(key) - - if err != nil { - req := ResponseStringStruct{Response: string(err.Error())} - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(req) - } else { - req := ResponseStringStruct{Response: "Key deletion successful."} - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(&req) - } -} diff --git a/src/dkv/api/endpointViews_fake.go b/src/dkv/api/endpointViews_fake.go deleted file mode 100644 index ecc6466..0000000 --- a/src/dkv/api/endpointViews_fake.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 "errors" - -/* -A ConsulStruct is added inside this so that FakeConsul becomes an implementation of the Consul interface. -If we don't add ConsulStruct inside this, it complains that the FakeConsul Struct doesn't implement all the methods -defined in Consul interface. -*/ -// Correct -type FakeConsul struct { - ConsulStruct -} - -func (f *FakeConsul) RequestGETS() ([]string, error) { - return []string{"key1", "key2"}, nil -} - -func (f *FakeConsul) RequestGET(key string) (string, error) { - return key, nil -} - -func (f *FakeConsul) RequestPUT(key string, value string) error { - return nil -} - -func (f *FakeConsul) RequestDELETE(key string) error { - return nil -} - -// Error -type FakeConsulErr struct { - ConsulStruct -} - -func (f *FakeConsulErr) RequestGETS() ([]string, error) { - return []string{"", ""}, errors.New("Internal Server Error") -} - -func (f *FakeConsulErr) RequestGET(key string) (string, error) { - return "", errors.New("Internal Server Error") -} - -func (f *FakeConsulErr) RequestPUT(key string, value string) error { - return errors.New("Internal Server Error") -} - -func (f *FakeConsulErr) RequestDELETE(key string) error { - return errors.New("Internal Server Error") -} - -/* -This is done similar to the fake Consul above to pass FakeKeyValues to the interface and control method's outputs -as required. -*/ -//Correct -type FakeKeyValues struct { - KeyValuesStruct -} - -func (f *FakeKeyValues) ReadConfigs(body POSTBodyStruct) error { - return nil -} - -func (f *FakeKeyValues) WriteKVsToConsul(prefix string) error { - return nil -} - -// Error -type FakeKeyValuesErr struct { - KeyValuesStruct -} - -func (f *FakeKeyValuesErr) ReadConfigs(body POSTBodyStruct) error { - return errors.New("Internal Server Error") -} - -func (f *FakeKeyValuesErr) WriteKVsToConsul(prefix string) error { - return errors.New("Internal Server Error") -} diff --git a/src/dkv/api/endpointViews_test.go b/src/dkv/api/endpointViews_test.go deleted file mode 100644 index 8e2799e..0000000 --- a/src/dkv/api/endpointViews_test.go +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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 ( - "bytes" - "encoding/json" - "github.com/gorilla/mux" - "github.com/stretchr/testify/assert" - "net/http" - "net/http/httptest" - "testing" -) - -func Router() *mux.Router { - router := mux.NewRouter() - router.HandleFunc("/loadconfigs", HandlePOST).Methods("POST") - router.HandleFunc("/getconfig/{key}", HandleGET).Methods("GET") - router.HandleFunc("/deleteconfig/{key}", HandleDELETE).Methods("DELETE") - router.HandleFunc("/getconfigs", HandleGETS).Methods("GET") - return router -} - -func TestHandleGETS(t *testing.T) { - oldConsul := Consul - Consul = &FakeConsul{} - defer func() { Consul = oldConsul }() - - request, _ := http.NewRequest("GET", "/getconfigs", nil) - response := httptest.NewRecorder() - Router().ServeHTTP(response, request) - - assert.Equal(t, 200, response.Code, "200 response is expected") -} - -func TestHandleGETS_err(t *testing.T) { - oldConsul := Consul - Consul = &FakeConsulErr{} - defer func() { Consul = oldConsul }() - - request, _ := http.NewRequest("GET", "/getconfigs", nil) - response := httptest.NewRecorder() - Router().ServeHTTP(response, request) - - assert.Equal(t, 400, response.Code, "400 response is expected") -} - -func TestHandleGET(t *testing.T) { - oldConsul := Consul - Consul = &FakeConsul{} - defer func() { Consul = oldConsul }() - - request, _ := http.NewRequest("GET", "/getconfig/key1", nil) - response := httptest.NewRecorder() - Router().ServeHTTP(response, request) - - assert.Equal(t, 200, response.Code, "200 response is expected") -} - -func TestHandleGET_err(t *testing.T) { - oldConsul := Consul - Consul = &FakeConsulErr{} - defer func() { Consul = oldConsul }() - - request, _ := http.NewRequest("GET", "/getconfig/key1", nil) - response := httptest.NewRecorder() - Router().ServeHTTP(response, request) - - assert.Equal(t, 400, response.Code, "400 response is expected") -} - -func TestHandlePOST(t *testing.T) { - oldConsul := Consul - oldKeyValues := KeyValues - - Consul = &FakeConsul{} - KeyValues = &FakeKeyValues{} - - defer func() { - Consul = oldConsul - KeyValues = oldKeyValues - }() - - body := &POSTBodyStruct{ - Domain: "test", - Type: &TypeStruct{ - FilePath: "default", - }, - } - - b, _ := json.Marshal(body) - - // json Marshal converts struct to json in Bytes. But bytes doesn't have - // io reader needed. So the byte is passed to NewBuffer. - request, _ := http.NewRequest("POST", "/loadconfigs", bytes.NewBuffer(b)) - response := httptest.NewRecorder() - Router().ServeHTTP(response, request) - - assert.Equal(t, 200, response.Code, "200 response is expected") -} - -func TestHandlePOST_no_body(t *testing.T) { - oldConsul := Consul - oldKeyValues := KeyValues - - Consul = &FakeConsul{} - KeyValues = &FakeKeyValues{} - - defer func() { - Consul = oldConsul - KeyValues = oldKeyValues - }() - - body := &POSTBodyStruct{} - - b, _ := json.Marshal(body) - - request, _ := http.NewRequest("POST", "/loadconfigs", bytes.NewBuffer(b)) - response := httptest.NewRecorder() - Router().ServeHTTP(response, request) - - assert.Equal(t, 400, response.Code, "400 response is expected") -} - -func TestHandlePOST_no_filepath(t *testing.T) { - oldConsul := Consul - oldKeyValues := KeyValues - - Consul = &FakeConsul{} - KeyValues = &FakeKeyValues{} - - defer func() { - Consul = oldConsul - KeyValues = oldKeyValues - }() - - body := &POSTBodyStruct{ - Type: &TypeStruct{}, - } - - b, _ := json.Marshal(body) - - request, _ := http.NewRequest("POST", "/loadconfigs", bytes.NewBuffer(b)) - response := httptest.NewRecorder() - Router().ServeHTTP(response, request) - - assert.Equal(t, 400, response.Code, "400 response is expected") -} - -func TestHandlePOST_ConsulError(t *testing.T) { - oldConsul := Consul - oldKeyValues := KeyValues - - Consul = &FakeConsulErr{} - KeyValues = &FakeKeyValuesErr{} - - defer func() { - Consul = oldConsul - KeyValues = oldKeyValues - }() - - body := &POSTBodyStruct{ - Domain: "test", - Type: &TypeStruct{ - FilePath: "default", - }, - } - - b, _ := json.Marshal(body) - - request, _ := http.NewRequest("POST", "/loadconfigs", bytes.NewBuffer(b)) - response := httptest.NewRecorder() - Router().ServeHTTP(response, request) - - assert.Equal(t, 500, response.Code, "500 response is expected") -} - -func TestHandleDELETE(t *testing.T) { - oldConsul := Consul - Consul = &FakeConsul{} - defer func() { Consul = oldConsul }() - - request, _ := http.NewRequest("DELETE", "/deleteconfig/key1", nil) - response := httptest.NewRecorder() - Router().ServeHTTP(response, request) - - assert.Equal(t, 200, response.Code, "200 response is expected") -} - -func TestHandleDELETE_err(t *testing.T) { - oldConsul := Consul - Consul = &FakeConsulErr{} - defer func() { Consul = oldConsul }() - - request, _ := http.NewRequest("DELETE", "/deleteconfig/key1", nil) - response := httptest.NewRecorder() - Router().ServeHTTP(response, request) - - assert.Equal(t, 400, response.Code, "400 response is expected") -} diff --git a/src/dkv/api/initialise.go b/src/dkv/api/initialise.go index 331c981..824ca81 100644 --- a/src/dkv/api/initialise.go +++ b/src/dkv/api/initialise.go @@ -16,18 +16,10 @@ package api -import ( - "errors" - "os" -) - func Initialise() error { - if os.Getenv("CONSUL_IP") == "" { - return errors.New("CONSUL_IP environment variable not set.") - } - Consul = &ConsulStruct{} KeyValues = &KeyValuesStruct{kvs: make(map[string]string)} + Directory = &DirectoryStruct{directory: ""} err := Consul.InitializeConsulClient() if err != nil { diff --git a/src/dkv/api/propertiesReader.go b/src/dkv/api/propertiesReader.go deleted file mode 100644 index 559c0fc..0000000 --- a/src/dkv/api/propertiesReader.go +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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 ( - "errors" - "github.com/magiconair/properties" - "io/ioutil" - "log" - "path" - "runtime" - "sync" -) - -type KeyValuesInterface interface { - WriteKVsToConsul(string) error - ReadConfigs(POSTBodyStruct) error - PropertiesFilesToKV(string) error - ReadMultipleProperties(string) error - ReadProperty(string) -} - -type KeyValuesStruct struct { - sync.RWMutex - kvs map[string]string -} - -var KeyValues KeyValuesInterface - -func (kvStruct *KeyValuesStruct) WriteKVsToConsul(prefix string) error { - for key, value := range kvStruct.kvs { - key = prefix + "." + key - err := Consul.RequestPUT(key, value) - if err != nil { - return err - } - log.Println("[INFO] Key: ", key, "| Value: ", value) - } - log.Println("[INFO] Wrote KVs to Consul.") - return nil -} - -func (kvStruct *KeyValuesStruct) ReadConfigs(body POSTBodyStruct) error { - defer kvStruct.Unlock() - - kvStruct.Lock() - - err := kvStruct.PropertiesFilesToKV(body.Type.FilePath) - if err != nil { - return err - } - return nil -} - -func (kvStruct *KeyValuesStruct) PropertiesFilesToKV(directory string) error { - - if directory == "default" { - _, filename, _, ok := runtime.Caller(0) - if !ok { - return errors.New("No caller") - } - - defaultDir := path.Dir(filename) + "/../configurations/" - err := kvStruct.ReadMultipleProperties(defaultDir) - if err != nil { - return err - } - - return nil - - } else { - directory += "/" - err := kvStruct.ReadMultipleProperties(directory) - if err != nil { - return err - } - - return nil - } -} - -func (kvStruct *KeyValuesStruct) ReadMultipleProperties(path string) error { - files, err := ioutil.ReadDir(path) - if err != nil { - return err - } - - for _, f := range files { - kvStruct.ReadProperty(path + f.Name()) - } - - return nil -} - -func (kvStruct *KeyValuesStruct) ReadProperty(path string) { - p := properties.MustLoadFile(path, properties.UTF8) - for _, key := range p.Keys() { - kvStruct.kvs[key] = p.MustGet(key) - } -} diff --git a/src/dkv/api/propertiesReader_test.go b/src/dkv/api/propertiesReader_test.go deleted file mode 100644 index 342542a..0000000 --- a/src/dkv/api/propertiesReader_test.go +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 - -// TODO(sshank) diff --git a/src/dkv/api/queryConsulHandlers.go b/src/dkv/api/queryConsulHandlers.go new file mode 100644 index 0000000..fb63b6d --- /dev/null +++ b/src/dkv/api/queryConsulHandlers.go @@ -0,0 +1,87 @@ +/* + * 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" + "github.com/gorilla/mux" + "net/http" +) + +type ResponseStringStruct struct { + Response string `json:"response"` +} + +type ResponseGETStruct struct { + Response map[string]string `json:"response"` +} + +type ResponseGETSStruct struct { + Response []string `json:"response"` +} + +func HandleGET(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + key := vars["token"] + "/" + vars["key"] + + value, err := Consul.RequestGET(key) + + if err != nil { + req := ResponseStringStruct{Response: string(err.Error())} + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(req) + } else { + req := ResponseGETStruct{Response: map[string]string{key: value}} + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(req) + } +} + +func HandleGETS(w http.ResponseWriter, r *http.Request) { + + values, err := Consul.RequestGETS() + + if err != nil { + req := ResponseStringStruct{Response: string(err.Error())} + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(req) + } else { + req := ResponseGETSStruct{Response: values} + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(req) + } +} + +func HandleDELETE(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + key := vars["key"] + + err := Consul.RequestDELETE(key) + + if err != nil { + req := ResponseStringStruct{Response: string(err.Error())} + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(req) + } else { + req := ResponseStringStruct{Response: "Key deletion successful."} + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&req) + } +} diff --git a/src/dkv/api/queryConsulHandlers_test.go b/src/dkv/api/queryConsulHandlers_test.go new file mode 100644 index 0000000..38f3acd --- /dev/null +++ b/src/dkv/api/queryConsulHandlers_test.go @@ -0,0 +1,105 @@ +/* + * 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 ( + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +func RouterConsul() *mux.Router { + router := mux.NewRouter() + router.HandleFunc("/v1/getconfig/{key}", HandleGET).Methods("GET") + router.HandleFunc("/v1/deleteconfig/{key}", HandleDELETE).Methods("DELETE") + router.HandleFunc("/v1/getconfigs", HandleGETS).Methods("GET") + return router +} + +func TestHandleGETS(t *testing.T) { + oldConsul := Consul + Consul = &FakeConsul{} + defer func() { Consul = oldConsul }() + + request, _ := http.NewRequest("GET", "/v1/getconfigs", nil) + response := httptest.NewRecorder() + RouterConsul().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleGETS_err(t *testing.T) { + oldConsul := Consul + Consul = &FakeConsulErr{} + defer func() { Consul = oldConsul }() + + request, _ := http.NewRequest("GET", "/v1/getconfigs", nil) + response := httptest.NewRecorder() + RouterConsul().ServeHTTP(response, request) + + assert.Equal(t, 400, response.Code, "400 response is expected") +} + +func TestHandleGET(t *testing.T) { + oldConsul := Consul + Consul = &FakeConsul{} + defer func() { Consul = oldConsul }() + + request, _ := http.NewRequest("GET", "/v1/getconfig/key1", nil) + response := httptest.NewRecorder() + RouterConsul().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleGET_err(t *testing.T) { + oldConsul := Consul + Consul = &FakeConsulErr{} + defer func() { Consul = oldConsul }() + + request, _ := http.NewRequest("GET", "/v1/getconfig/key1", nil) + response := httptest.NewRecorder() + RouterConsul().ServeHTTP(response, request) + + assert.Equal(t, 400, response.Code, "400 response is expected") +} + +func TestHandleDELETE(t *testing.T) { + oldConsul := Consul + Consul = &FakeConsul{} + defer func() { Consul = oldConsul }() + + request, _ := http.NewRequest("DELETE", "/v1/deleteconfig/key1", nil) + response := httptest.NewRecorder() + RouterConsul().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleDELETE_err(t *testing.T) { + oldConsul := Consul + Consul = &FakeConsulErr{} + defer func() { Consul = oldConsul }() + + request, _ := http.NewRequest("DELETE", "/v1/deleteconfig/key1", nil) + response := httptest.NewRecorder() + RouterConsul().ServeHTTP(response, request) + + assert.Equal(t, 400, response.Code, "400 response is expected") +} diff --git a/src/dkv/api/registrationHandlers.go b/src/dkv/api/registrationHandlers.go new file mode 100644 index 0000000..1c50b33 --- /dev/null +++ b/src/dkv/api/registrationHandlers.go @@ -0,0 +1,173 @@ +/* + * 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" + "errors" + "github.com/gorilla/mux" + "net/http" +) + +type CreateRegisterServiceBody struct { + Domain string `json:"domain"` +} + +type CreateServiceSubdomainBody struct { + Subdomain string `json:"subdomain"` +} + +func ValidateCreateRegisterServiceBody(body CreateRegisterServiceBody) error { + if body.Domain == "" { + return errors.New("Domain not set. Please set domain in POST.") + } + if body.Domain == "default" { + return errors.New("Domain not allowed. Please set another domain in POST.") + } + return nil +} + +/* + TODO(sshank): Add validations to check if tokens/sub-domains/files indeed + exist in the token_service JSON or in the directory. This is to avoid the service + returning the file system errors to the user. +*/ + +func HandleServiceCreate(w http.ResponseWriter, r *http.Request) { + var body CreateRegisterServiceBody + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&body) + + if err != nil { + GenerateResponse(w, r, http.StatusBadRequest, "Empty body.") + return + } + + err = ValidateCreateRegisterServiceBody(body) + + if err != nil { + GenerateResponse(w, r, http.StatusBadRequest, string(err.Error())) + return + } + + token, err := Directory.CreateService(body) + + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + } else { + GenerateResponse(w, r, http.StatusOK, "Registration Successful. Token: "+token) + } +} + +func HandleServiceGet(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + token := vars["token"] + + if token == "" { + GenerateResponse(w, r, http.StatusBadRequest, "Token not present in path.") + return + } + + service, found, err := Directory.FindService(token) + + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + } else { + if found == true { + GenerateResponse(w, r, http.StatusOK, service) + } else { + GenerateResponse(w, r, http.StatusNotFound, "Service for Token:"+token+"not found.") + } + + } +} + +func HandleServiceDelete(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + token := vars["token"] + + if token == "default" { + GenerateResponse(w, r, http.StatusNotAcceptable, "Default delete not allowed.") + return + } + + err := Directory.RemoveService(token) + + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + } else { + GenerateResponse(w, r, http.StatusOK, "Deletion of service is successful.") + } +} + +func HandleServiceSubdomainCreate(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + token := vars["token"] + + var body CreateServiceSubdomainBody + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&body) + + if err != nil { + GenerateResponse(w, r, http.StatusBadRequest, "Empty body.") + return + } + + if body.Subdomain == "" { + GenerateResponse(w, r, http.StatusBadRequest, "Subdomain not found in POST.") + return + } + + err = Directory.CreateServiceSubdomain(token, body.Subdomain) + + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + } else { + GenerateResponse(w, r, http.StatusOK, "Subdomain creation success with token: "+token) + } + +} + +func HandleServiceSubdomainGet(w http.ResponseWriter, r *http.Request) { + // TODO(sshank): This will list all subdomain in a service. +} + +func HandleServiceSubdomainDelete(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + token := vars["token"] + subdomain := vars["subdomain"] + + if token == "" { + GenerateResponse(w, r, http.StatusBadRequest, "Token not passed.") + return + } + + if token == "default" && subdomain == "" { + GenerateResponse(w, r, http.StatusNotAcceptable, "Not allowerd.") + return + } + + err := Directory.RemoveServiceSubdomain(token, subdomain) + + if err != nil { + GenerateResponse(w, r, http.StatusInternalServerError, string(err.Error())) + } else { + GenerateResponse(w, r, http.StatusOK, "Deletion of service is successful.") + } +} diff --git a/src/dkv/api/registrationHandlers_test.go b/src/dkv/api/registrationHandlers_test.go new file mode 100644 index 0000000..3482e8d --- /dev/null +++ b/src/dkv/api/registrationHandlers_test.go @@ -0,0 +1,276 @@ +/* + * 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 ( + "bytes" + "encoding/json" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +func RouterRegister() *mux.Router { + router := mux.NewRouter() + router.HandleFunc("/v1/register", HandleServiceCreate).Methods("POST") + router.HandleFunc("/v1/register/{token}", HandleServiceGet).Methods("GET") + router.HandleFunc("/v1/register/{token}", HandleServiceDelete).Methods("DELETE") + return router +} + +func RouterRegisterSubdomain() *mux.Router { + router := mux.NewRouter() + router.HandleFunc("/v1/register/{token}/subdomain", HandleServiceSubdomainCreate).Methods("POST") + router.HandleFunc("/v1/register/{token}/subdomain/{subdomain}", HandleServiceSubdomainDelete).Methods("DELETE") + return router +} + +func TestHandleServiceCreate(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + body := &CreateRegisterServiceBody{ + Domain: "test", + } + + b, _ := json.Marshal(body) + + request, _ := http.NewRequest("POST", "/v1/register", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterRegister().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleServiceCreate_default(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + body := &CreateRegisterServiceBody{ + Domain: "default", + } + + b, _ := json.Marshal(body) + + request, _ := http.NewRequest("POST", "/v1/register", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterRegister().ServeHTTP(response, request) + + assert.Equal(t, 400, response.Code, "400 response is expected") +} +func TestHandleServiceCreate_no_body(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + body := &CreateRegisterServiceBody{} + + b, _ := json.Marshal(body) + + request, _ := http.NewRequest("POST", "/v1/register", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterRegister().ServeHTTP(response, request) + + assert.Equal(t, 400, response.Code, "400 response is expected") +} + +func TestHandleServiceCreate_no_domain(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + body := &CreateRegisterServiceBody{ + Domain: "", + } + + b, _ := json.Marshal(body) + + request, _ := http.NewRequest("POST", "/v1/register", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterRegister().ServeHTTP(response, request) + + assert.Equal(t, 400, response.Code, "400 response is expected") +} + +func TestHandleServiceGet(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("GET", "/v1/register/token1", nil) + response := httptest.NewRecorder() + RouterRegister().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleServiceGet_err(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectoryErr{} + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("GET", "/v1/register/token1", nil) + response := httptest.NewRecorder() + RouterRegister().ServeHTTP(response, request) + + assert.Equal(t, 500, response.Code, "500 response is expected") +} +func TestHandleServiceDelete(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("DELETE", "/v1/register/token1", nil) + response := httptest.NewRecorder() + RouterRegister().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleServiceDelete_default(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("DELETE", "/v1/register/default", nil) + response := httptest.NewRecorder() + RouterRegister().ServeHTTP(response, request) + + assert.Equal(t, 406, response.Code, "406 response is expected") +} + +func TestHandleServiceDelete_err(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectoryErr{} + + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("DELETE", "/v1/register/token1", nil) + response := httptest.NewRecorder() + RouterRegister().ServeHTTP(response, request) + + assert.Equal(t, 500, response.Code, "500 response is expected") +} + +func TestHandleServiceSuddomainCreate(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + body := &CreateServiceSubdomainBody{ + Subdomain: "test", + } + + b, _ := json.Marshal(body) + + request, _ := http.NewRequest("POST", "/v1/register/token1/subdomain", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterRegisterSubdomain().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleServiceSuddomainCreate_no_body(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + body := &CreateServiceSubdomainBody{} + + b, _ := json.Marshal(body) + + request, _ := http.NewRequest("POST", "/v1/register/token1/subdomain", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterRegisterSubdomain().ServeHTTP(response, request) + + assert.Equal(t, 400, response.Code, "400 response is expected") +} + +func TestHandleServiceSuddomainCreate_no_subdomain(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + body := &CreateServiceSubdomainBody{ + Subdomain: "", + } + + b, _ := json.Marshal(body) + + request, _ := http.NewRequest("POST", "/v1/register/token1/subdomain", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterRegisterSubdomain().ServeHTTP(response, request) + + assert.Equal(t, 400, response.Code, "400 response is expected") +} + +func TestHandleServiceSuddomainCreate_err(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectoryErr{} + + defer func() { Directory = oldDirectory }() + + body := &CreateServiceSubdomainBody{ + Subdomain: "test", + } + + b, _ := json.Marshal(body) + + request, _ := http.NewRequest("POST", "/v1/register/token1/subdomain", bytes.NewBuffer(b)) + response := httptest.NewRecorder() + RouterRegisterSubdomain().ServeHTTP(response, request) + + assert.Equal(t, 500, response.Code, "500 response is expected") +} + +func TestHandleServiceSuddomainDelete(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectory{} + + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("DELETE", "/v1/register/token1/subdomain/subdomain1", nil) + response := httptest.NewRecorder() + RouterRegisterSubdomain().ServeHTTP(response, request) + + assert.Equal(t, 200, response.Code, "200 response is expected") +} + +func TestHandleServiceSuddomainDelete_err(t *testing.T) { + oldDirectory := Directory + Directory = &FakeDirectoryErr{} + + defer func() { Directory = oldDirectory }() + + request, _ := http.NewRequest("DELETE", "/v1/register/token1/subdomain/subdomain1", nil) + response := httptest.NewRecorder() + RouterRegisterSubdomain().ServeHTTP(response, request) + + assert.Equal(t, 500, response.Code, "500 response is expected") +} diff --git a/src/dkv/api/token_service_map.json b/src/dkv/api/token_service_map.json new file mode 100644 index 0000000..51d4dbb --- /dev/null +++ b/src/dkv/api/token_service_map.json @@ -0,0 +1 @@ +[{"token":"default","service":"default"}] \ No newline at end of file diff --git a/src/dkv/api/utils.go b/src/dkv/api/utils.go new file mode 100644 index 0000000..c1094ab --- /dev/null +++ b/src/dkv/api/utils.go @@ -0,0 +1,153 @@ +/* + * 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" + "errors" + "io/ioutil" + "net/http" +) + +var ( + IoutilRead = ioutil.ReadFile + IoutilWrite = ioutil.WriteFile + JsonReader = ReadJSON +) + +type Token_service_map struct { + Token string `json:"token"` + Service string `json:"service"` +} + +func ReadJSON(path string) ([]Token_service_map, error) { + var tsm_list []Token_service_map + // raw, err := ioutil.ReadFile("./token_service_map.json") + raw, err := IoutilRead(path) + if err != nil { + return tsm_list, err + } + json.Unmarshal(raw, &tsm_list) + return tsm_list, nil +} + +func WriteJSON(path string, token string, service string) error { + tsm_list, err := JsonReader(path) + if err != nil { + return err + } + var tsm Token_service_map + tsm.Token = token + tsm.Service = service + tsm_list = append(tsm_list, tsm) + raw, err := json.Marshal(tsm_list) + if err != nil { + return err + } + err = IoutilWrite(path, raw, 0644) + if err != nil { + return err + } + return nil +} + +func DeleteInJSON(path string, token string) error { + serviceList, err := JsonReader(path) + if err != nil { + return err + } + + var resultList []Token_service_map + var foundFlag = false + + // Linear search for the token. If found set found flag. If not, keep + // copying the different values to resultList. + for _, service := range serviceList { + if service.Token == token { + foundFlag = true + } else { + resultList = append(resultList, service) + } + } + + if foundFlag == false { + return errors.New("Service not found. Check if Token is correct or service is registered.") + } else { + // This is done to avoid writing 'null' in the json file. + if len(serviceList) == 1 { + dummy := Token_service_map{Token: "", Service: ""} + resultList = append(resultList, dummy) + } + raw, err := json.Marshal(resultList) + if err != nil { + return err + } + err = IoutilWrite(path, raw, 0644) + if err != nil { + return err + } + return nil + } +} + +func FindTokenInJSON(path string, token string) (bool, error) { + serviceList, err := JsonReader(path) + if err != nil { + return false, err + } + + for _, service := range serviceList { + if service.Token == token { + return true, nil + } + } + return false, nil +} + +func FindServiceInJSON(path string, serviceName string) (bool, error) { + serviceList, err := JsonReader(path) + if err != nil { + return false, err + } + + for _, service := range serviceList { + if service.Service == serviceName { + return true, nil + } + } + return false, nil +} + +func GetServicebyToken(path string, token string) (string, bool, error) { + serviceList, err := JsonReader(path) + if err != nil { + return "", false, err + } + for _, service := range serviceList { + if service.Token == token { + return service.Service, true, nil + } + } + return "", false, nil +} + +func GenerateResponse(w http.ResponseWriter, r *http.Request, httpStatus int, msg string) { + req := ResponseStringStruct{Response: msg} + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(httpStatus) + json.NewEncoder(w).Encode(req) +} diff --git a/src/dkv/api/utils_test.go b/src/dkv/api/utils_test.go new file mode 100644 index 0000000..0bca7c5 --- /dev/null +++ b/src/dkv/api/utils_test.go @@ -0,0 +1,306 @@ +/* + * 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 ( + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestReadJSON(t *testing.T) { + oldIoutilRead := IoutilRead + + defer func() { + IoutilRead = oldIoutilRead + }() + + IoutilRead = func(path string) ([]byte, error) { + return []byte("test"), nil + } + + _, err := ReadJSON("path") + assert.Equal(t, nil, err, "Error should be nil.") +} +func TestReadJSON_err(t *testing.T) { + oldIoutilRead := IoutilRead + + defer func() { + IoutilRead = oldIoutilRead + }() + + _, err := ReadJSON("path") + assert.NotNil(t, err, "Err should not be nil.") +} + +func TestWriteJSON(t *testing.T) { + oldReadJson := JsonReader + oldIoutilWrite := IoutilWrite + + defer func() { + JsonReader = oldReadJson + IoutilWrite = oldIoutilWrite + }() + + JsonReader = func(path string) ([]Token_service_map, error) { + var tsm_list []Token_service_map + o1 := Token_service_map{ + Token: "token1", + Service: "service1", + } + o2 := Token_service_map{ + Token: "token2", + Service: "service2", + } + tsm_list = append(tsm_list, o1, o2) + return tsm_list, nil + } + + IoutilWrite = func(val string, b []byte, f os.FileMode) error { + return nil + } + + err := WriteJSON("path", "token3", "service3") + assert.Equal(t, nil, err, "Error should be nil.") + +} + +func TestDeleteInJSON(t *testing.T) { + oldReadJson := JsonReader + defer func() { + JsonReader = oldReadJson + }() + + JsonReader = func(path string) ([]Token_service_map, error) { + var tsm_list []Token_service_map + o1 := Token_service_map{ + Token: "token1", + Service: "service1", + } + o2 := Token_service_map{ + Token: "token2", + Service: "service2", + } + tsm_list = append(tsm_list, o1, o2) + return tsm_list, nil + } + + IoutilWrite = func(val string, b []byte, f os.FileMode) error { + return nil + } + + err := DeleteInJSON("path", "token2") + assert.Equal(t, nil, err, "Error should be nil.") +} + +func TestDeleteInJSON_single(t *testing.T) { + oldReadJson := JsonReader + defer func() { + JsonReader = oldReadJson + }() + + JsonReader = func(path string) ([]Token_service_map, error) { + var tsm_list []Token_service_map + o1 := Token_service_map{ + Token: "token1", + Service: "service1", + } + tsm_list = append(tsm_list, o1) + return tsm_list, nil + } + + IoutilWrite = func(val string, b []byte, f os.FileMode) error { + return nil + } + + err := DeleteInJSON("path", "token1") + assert.Equal(t, nil, err, "Error should be nil.") +} + +func TestDeleteInJSON_not_found(t *testing.T) { + oldReadJson := JsonReader + defer func() { + JsonReader = oldReadJson + }() + + JsonReader = func(path string) ([]Token_service_map, error) { + var tsm_list []Token_service_map + o1 := Token_service_map{ + Token: "token1", + Service: "service1", + } + o2 := Token_service_map{ + Token: "token2", + Service: "service2", + } + tsm_list = append(tsm_list, o1, o2) + return tsm_list, nil + } + + IoutilWrite = func(val string, b []byte, f os.FileMode) error { + return nil + } + + err := DeleteInJSON("path", "token3") + assert.NotNil(t, err, "Err should not be nil.") +} + +func TestFindTokenInJSON(t *testing.T) { + oldReadJson := JsonReader + defer func() { + JsonReader = oldReadJson + }() + + JsonReader = func(path string) ([]Token_service_map, error) { + var tsm_list []Token_service_map + o1 := Token_service_map{ + Token: "token1", + Service: "service1", + } + o2 := Token_service_map{ + Token: "token2", + Service: "service2", + } + tsm_list = append(tsm_list, o1, o2) + return tsm_list, nil + } + + IoutilWrite = func(val string, b []byte, f os.FileMode) error { + return nil + } + + found, _ := FindTokenInJSON("path", "token2") + + assert.True(t, found, "Token should be found in JSON.") +} + +func TestFindServiceInJSON(t *testing.T) { + oldReadJson := JsonReader + defer func() { + JsonReader = oldReadJson + }() + + JsonReader = func(path string) ([]Token_service_map, error) { + var tsm_list []Token_service_map + o1 := Token_service_map{ + Token: "token1", + Service: "service1", + } + o2 := Token_service_map{ + Token: "token2", + Service: "service2", + } + tsm_list = append(tsm_list, o1, o2) + return tsm_list, nil + } + + IoutilWrite = func(val string, b []byte, f os.FileMode) error { + return nil + } + + found, _ := FindServiceInJSON("path", "service2") + + assert.True(t, found, "Token should be found in JSON.") +} + +func TestFindServiceInJSON_not_found(t *testing.T) { + oldReadJson := JsonReader + defer func() { + JsonReader = oldReadJson + }() + + JsonReader = func(path string) ([]Token_service_map, error) { + var tsm_list []Token_service_map + o1 := Token_service_map{ + Token: "token1", + Service: "service1", + } + o2 := Token_service_map{ + Token: "token2", + Service: "service2", + } + tsm_list = append(tsm_list, o1, o2) + return tsm_list, nil + } + + IoutilWrite = func(val string, b []byte, f os.FileMode) error { + return nil + } + + found, _ := FindServiceInJSON("path", "service3") + + assert.False(t, found, "Token should not be found in JSON.") +} + +func TestGetServicebyToken(t *testing.T) { + oldReadJson := JsonReader + defer func() { + JsonReader = oldReadJson + }() + + JsonReader = func(path string) ([]Token_service_map, error) { + var tsm_list []Token_service_map + o1 := Token_service_map{ + Token: "token1", + Service: "service1", + } + o2 := Token_service_map{ + Token: "token2", + Service: "service2", + } + tsm_list = append(tsm_list, o1, o2) + return tsm_list, nil + } + + IoutilWrite = func(val string, b []byte, f os.FileMode) error { + return nil + } + + service, found, _ := GetServicebyToken("path", "token1") + + assert.Equal(t, "service1", service, "Service not found") + assert.True(t, found, "Token should be found in JSON.") +} + +func TestGetServicebyToken_not_found(t *testing.T) { + oldReadJson := JsonReader + defer func() { + JsonReader = oldReadJson + }() + + JsonReader = func(path string) ([]Token_service_map, error) { + var tsm_list []Token_service_map + o1 := Token_service_map{ + Token: "token1", + Service: "service1", + } + o2 := Token_service_map{ + Token: "token2", + Service: "service2", + } + tsm_list = append(tsm_list, o1, o2) + return tsm_list, nil + } + + IoutilWrite = func(val string, b []byte, f os.FileMode) error { + return nil + } + + service, found, _ := GetServicebyToken("path", "token3") + + assert.Equal(t, "", service, "Service is found") + assert.False(t, found, "Token should be found in JSON.") +} -- cgit 1.2.3-korg