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 --- README.md | 39 ++- mountpath/default/sampleAAIConfig.properties | 94 +++++++ mountpath/default/sampleAPPCConfig.properties | 113 ++++++++ src/dkv/Gopkg.lock | 8 +- 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 +++++++++++++++++++++ src/dkv/configurations/sampleAAIConfig.properties | 94 ------- src/dkv/configurations/sampleAPPCConfig.properties | 113 -------- src/dkv/main.go | 27 +- 28 files changed, 2428 insertions(+), 972 deletions(-) create mode 100644 mountpath/default/sampleAAIConfig.properties create mode 100644 mountpath/default/sampleAPPCConfig.properties 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 delete mode 100644 src/dkv/configurations/sampleAAIConfig.properties delete mode 100644 src/dkv/configurations/sampleAPPCConfig.properties diff --git a/README.md b/README.md index 0273978..ac3b1f9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,39 @@ Distributed Key Value Store using Consul to store application configuration data. -# TODO -# Add documentation on how to run. +# TODO (Add documentation on how to run) + +# Sample Curl examples: + +## Load default configuration +`curl -X GET localhost:8080/v1/config/load-default` + +## Register new domain +`curl -X POST -d '{"domain":""}' localhost:8080/v1/register` +`export TOKEN=` +## Register new sub domain +`curl -X POST -d '{"subdomain":""}' localhost:8080/v1/register/$TOKEN/subdomain` + +## Check if a domain is already registered. +`curl -X GET localhost:8080/v1/register/$TOKEN` + +## List all sub domains in a domain. +`TODO` + +## Upload properties file to domain or subdomain. +`curl -X POST -F 'token=$TOKEN' -F 'configFile=@./example.properties' localhost:8080/v1/config` +`curl -X POST -F 'token=$TOKEN' -F 'subdomain=' -F 'configFile=@./example.properties' localhost:8080/v1/config` + +## Load properties file into Consul +`curl -X POST -d '{"token":"$TOKEN", "filename": "example.properties"}' localhost:8080/v1/config/load` + +## Fetch properties file +`curl -X GET localhost:8080/v1/config/$TOKEN/example.properties` +`curl -X GET localhost:8080/v1/config/$TOKEN//example.properties` + +## Delete properties file +`curl -X DELETE localhost:8080/v1/config/$TOKEN/example.properties` +`curl -X DELETE localhost:8080/v1/config/$TOKEN//example.properties` + +## Delete project/sub project +`curl -X DELETE localhost:8080/v1/register/$TOKEN/subdomain/` +`curl -X DELETE localhost:8080/v1/register/$TOKEN` diff --git a/mountpath/default/sampleAAIConfig.properties b/mountpath/default/sampleAAIConfig.properties new file mode 100644 index 0000000..6052315 --- /dev/null +++ b/mountpath/default/sampleAAIConfig.properties @@ -0,0 +1,94 @@ +#################################################################### +# REMEMBER TO THINK ABOUT ENVIRONMENTAL DIFFERENCES AND CHANGE THE +# TEMPLATE AND *ALL* DATAFILES +#################################################################### + +aai.config.checktime=1000 + +# this could come from siteconfig.pl? +aai.config.nodename=AutomaticallyOverwritten + + + +aai.auth.cspcookies_on=false +aai.dbmodel.filename=ex5.json + +aai.server.url.base=<%= @AAI_SERVER_URL_BASE %> +aai.server.url=<%= @AAI_SERVER_URL %> +aai.global.callback.url=<%= @AAI_GLOBAL_CALLBACK_URL %> + +aai.tools.enableBasicAuth=true +aai.tools.username=AAI +aai.tools.password=AAI + +aai.truststore.filename=<%= @AAI_TRUSTSTORE_FILENAME %> +aai.truststore.passwd.x=<%= @AAI_TRUSTSTORE_PASSWD_X %> +aai.keystore.filename=<%= @AAI_KEYSTORE_FILENAME %> +aai.keystore.passwd.x=<%= @AAI_KEYSTORE_PASSWD_X %> + + +aai.notification.current.version=<%= @AAI_NOTIFICATION_CURRENT_VERSION %> +aai.notificationEvent.default.status=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_EVENT_STATUS %> +aai.notificationEvent.default.eventType=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_EVENT_TYPE %> +aai.notificationEvent.default.domain=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_DOMAIN %> +aai.notificationEvent.default.sourceName=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_SOURCE_NAME %> +aai.notificationEvent.default.sequenceNumber=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_SEQUENCE_NUMBER %> +aai.notificationEvent.default.severity=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_SEVERITY %> +aai.notificationEvent.default.version=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_VERSION %> +# This one lets us enable/disable resource-version checking on updates/deletes +aai.resourceversion.enableflag=<%= @RESOURCE_VERSION_ENABLE_FLAG %> +aai.logging.maxStackTraceEntries=10 +aai.default.api.version=<%= @AAI_DEFAULT_API_VERSION %> + + + +# Used by Model-processing code +aai.model.delete.sleep.per.vtx.msec=500 +aai.model.query.resultset.maxcount=50 +aai.model.query.timeout.sec=90 + +# Used by Data Grooming +aai.grooming.default.max.file=150 +aai.grooming.default.sleep.minutes=7 + +aai.model.proc.max.levels=50 +aai.edgeTag.proc.max.levels=50 + +# for transaction log +aai.logging.hbase.interceptor=true +aai.logging.hbase.enabled=true +aai.logging.hbase.logrequest=true +aai.logging.hbase.logresponse=true + +# for gremlin server +aai.server.rebind=g +hbase.table.name=<%= @TXN_HBASE_TABLE_NAME %> +hbase.table.timestamp.format=YYYYMMdd-HH:mm:ss:SSS +hbase.zookeeper.quorum=<%= @TXN_ZOOKEEPER_QUORUM %> +hbase.zookeeper.property.clientPort=<%= @TXN_ZOOKEEPER_PROPERTY_CLIENTPORT %> +hbase.zookeeper.znode.parent=<%= @TXN_HBASE_ZOOKEEPER_ZNODE_PARENT %> + +aai.logging.trace.enabled=true +aai.logging.trace.logrequest=false +aai.logging.trace.logresponse=false + + +aai.transaction.logging=true +aai.transaction.logging.get=false +aai.transaction.logging.post=false + +#limit set for bulk consumer APIS +aai.bulkconsumer.payloadlimit=30 + +#uncomment and use header X-OverrideLimit with the value to override the bulk api limit +#aai.bulkconsumer.payloadoverride=AAI-OVERRIDE-KEY +aai.bulkconsumer.payloadoverride=false + +#timeout for crud enabled flag +aai.crud.timeoutenabled=true + +#timeout app specific +aai.crud.timeout.appspecific=JUNITTESTAPP1,1|JUNITTESTAPP2,-1|DCAE-CCS,-1|DCAES,-1|AAI-FILEGEN-GFPIP,-1 + +#default timeout limit added for traversal if not overridden (in ms) +aai.crud.timeoutlimit=180000 \ No newline at end of file diff --git a/mountpath/default/sampleAPPCConfig.properties b/mountpath/default/sampleAPPCConfig.properties new file mode 100644 index 0000000..484337f --- /dev/null +++ b/mountpath/default/sampleAPPCConfig.properties @@ -0,0 +1,113 @@ +### ### +### Properties for demo ### +### ### +appc.demo.poolMembers=10.0.11.1:3904 +appc.demo.topic.read=APPC-CL +appc.demo.topic.write=APPC-CL +appc.demo.client.name=appcDemoEventListener +appc.demo.threads.queuesize.min=1 +appc.demo.threads.queuesize.max=1000 +appc.demo.threads.poolsize.min=1 +appc.demo.threads.poolsize.max=2 +appc.demo.provider.user=admin +appc.demo.provider.pass=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U +appc.demo.provider.url=http://localhost:8181/restconf/operations/appc-provider +appc.provider.vfodl.url=http://admin:Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U@10.0.2.1:8282/restconf/config/network-topology:network-topology/topology/topology-netconf/node/NODE_NAME/yang-ext:mount/sample-plugin:sample-plugin/pg-streams/ + +# The properties right below are needed to properly call the Master DG to serve demo purposes +appc.service.logic.module.name=APPC +appc.topology.dg.method=topology-operation-all +appc.topology.dg.version=2.0.0 + +# TEMP - Properties that might be needed to make the AAI-APPC connection +org.onap.appc.db.url.appcctl=jdbc:mysql://dbhost:3306/appcctl +org.onap.appc.db.user.appcctl=appcctl +org.onap.appc.db.pass.appcctl=appcctl + +org.onap.appc.db.url.sdnctl=jdbc:mysql://dbhost:3306/sdnctl +org.onap.appc.db.user.sdnctl=sdnctl +org.onap.appc.db.pass.sdnctl=gamma + + +### ### +### OpenStack credentials (these properties also are used in appc-rest-adapter-bundle, appc-chef-adapter-bundle, appc-iaas-adapter-bundle) ### +### ### +provider1.type=OpenStackProvider +provider1.name=OpenStack +provider1.identity=http://localhost:8181/apidoc/explorer/index.html +provider1.tenant1.name=default +provider1.tenant1.domain=default +provider1.tenant1.userid=admin +provider1.tenant1.password=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U + + + + + +### ### +### Properties that are not covered or being replaced from default.properties files. Default value for DMaaP IP is 10.0.11.1:3904 ### +### which is what the Master HEAT Template to instantiate ONAP is pointing to (version R1). All other default values are ### +### left there since these are pre-defined as part of APP-C/ONAP default instantiation with Master HEAT Template ### +### ### + + +# Property below is valid in appc-command-executor-core, appc-license-manager-core, appc-lifecycle-management-core, +# appc-request-handler-core, appc-workflow-management-core (all from the appc-dispatcher package). +dmaap.poolMembers=10.0.11.1:3904 + + +# appc-event-listener-bundle properties (only defined in src/test of default.properties) +appc.LCM.poolMembers=10.0.11.1:3904 +appc.LCM.topic.read=APPC-LCM-READ +appc.LCM.topic.write=APPC-LCM-WRITE +appc.LCM.client.name=APPC-EVENT-LISTENER-TEST +appc.LCM.provider.user=admin +appc.LCM.provider.pass=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U +appc.LCM.provider.url=http://localhost:8181/restconf/operations/appc-provider-lcm + + +# properties from appc-netconf-adapter-bundle, appc-dg-common, appc-dmaap-adapter-bundle +poolMembers=10.0.11.1:3904 +event.pool.members=10.0.11.1:3904 +restconf.user=admin +restconf.pass=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U + + +# properties found in appc-rest-adapter-bundle, appc-chef-adapter-bundle, appc-iaas-adapter-bundle) +#Your OpenStack IP +test.ip=10.0.11.100 +# Your OpenStack Platform's Keystone Port (default is 5000) +test.port=5000 +test.tenantid=test +test.vmid=test +# Port 8774 below is default port for OpenStack's Nova API Service +test.url=http://api.appc.local/vm/9999999/test/99999999-9999-9999-9999-999999999999 +#skips hypervisor check which usually occurs during iaas-adapter-bundle startup +org.onap.appc.iaas.skiphypervisorcheck=true + + +# Properties from default.properties in the src/test and src/main paths of appc-asdc-listener-bundle +appc.sdc.host=10.0.3.1:8443 +appc.sdc.env=APPC-ASDC-ENV +appc.sdc.user=test +appc.sdc.pass=test +appc.sdc.consumer=APPC-ASDC-CONSUMER +appc.sdc.consumer.id=APPC-ASDC-CONSUMER-ID +appc.sdc.provider.url=http://localhost:8181/restconf/operations/AsdcMessage:configuration-document-request + +# Properties used by EventSenderDmaapImpl.java +DCAE.dmaap.event.topic.write=EventSenderTest +DCAE.dmaap.appc.username=test +DCAE.dmaap.appc.password=test +DCAE.dmaap.event.pool.members=10.0.11.1:3904 + +# OAM Listener +appc.OAM.disabled=true +appc.OAM.provider.url=http://localhost:8181/restconf/operations/appc-oam +appc.OAM.poolMembers=10.0.11.1:3904 +appc.OAM.service=ueb +appc.OAM.topic.read=testOAM +appc.OAM.topic.write=testOAM +appc.OAM.client.name=testOAM +appc.OAM.provider.user=admin +appc.OAM.provider.pass=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U \ No newline at end of file diff --git a/src/dkv/Gopkg.lock b/src/dkv/Gopkg.lock index 535922e..687a4d3 100644 --- a/src/dkv/Gopkg.lock +++ b/src/dkv/Gopkg.lock @@ -43,6 +43,12 @@ packages = ["."] revision = "6bb64b370b90e7ef1fa532be9e591a81c3493e00" +[[projects]] + branch = "master" + name = "github.com/hashicorp/go-uuid" + packages = ["."] + revision = "27454136f0364f2d44b1276c552d69105cf8c498" + [[projects]] name = "github.com/hashicorp/serf" packages = ["coordinate"] @@ -76,6 +82,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "000a855a3e5fca6c1f7b134951013777f7decbbde7be20af1e262da6005e874a" + inputs-digest = "ec895bb8f93ec798d68a3d83b9004eff00dc4078f5a2bccf75390bcb8fccbd72" solver-name = "gps-cdcl" solver-version = 1 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.") +} diff --git a/src/dkv/configurations/sampleAAIConfig.properties b/src/dkv/configurations/sampleAAIConfig.properties deleted file mode 100644 index 6052315..0000000 --- a/src/dkv/configurations/sampleAAIConfig.properties +++ /dev/null @@ -1,94 +0,0 @@ -#################################################################### -# REMEMBER TO THINK ABOUT ENVIRONMENTAL DIFFERENCES AND CHANGE THE -# TEMPLATE AND *ALL* DATAFILES -#################################################################### - -aai.config.checktime=1000 - -# this could come from siteconfig.pl? -aai.config.nodename=AutomaticallyOverwritten - - - -aai.auth.cspcookies_on=false -aai.dbmodel.filename=ex5.json - -aai.server.url.base=<%= @AAI_SERVER_URL_BASE %> -aai.server.url=<%= @AAI_SERVER_URL %> -aai.global.callback.url=<%= @AAI_GLOBAL_CALLBACK_URL %> - -aai.tools.enableBasicAuth=true -aai.tools.username=AAI -aai.tools.password=AAI - -aai.truststore.filename=<%= @AAI_TRUSTSTORE_FILENAME %> -aai.truststore.passwd.x=<%= @AAI_TRUSTSTORE_PASSWD_X %> -aai.keystore.filename=<%= @AAI_KEYSTORE_FILENAME %> -aai.keystore.passwd.x=<%= @AAI_KEYSTORE_PASSWD_X %> - - -aai.notification.current.version=<%= @AAI_NOTIFICATION_CURRENT_VERSION %> -aai.notificationEvent.default.status=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_EVENT_STATUS %> -aai.notificationEvent.default.eventType=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_EVENT_TYPE %> -aai.notificationEvent.default.domain=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_DOMAIN %> -aai.notificationEvent.default.sourceName=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_SOURCE_NAME %> -aai.notificationEvent.default.sequenceNumber=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_SEQUENCE_NUMBER %> -aai.notificationEvent.default.severity=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_SEVERITY %> -aai.notificationEvent.default.version=<%= @AAI_NOTIFICATION_EVENT_DEFAULT_VERSION %> -# This one lets us enable/disable resource-version checking on updates/deletes -aai.resourceversion.enableflag=<%= @RESOURCE_VERSION_ENABLE_FLAG %> -aai.logging.maxStackTraceEntries=10 -aai.default.api.version=<%= @AAI_DEFAULT_API_VERSION %> - - - -# Used by Model-processing code -aai.model.delete.sleep.per.vtx.msec=500 -aai.model.query.resultset.maxcount=50 -aai.model.query.timeout.sec=90 - -# Used by Data Grooming -aai.grooming.default.max.file=150 -aai.grooming.default.sleep.minutes=7 - -aai.model.proc.max.levels=50 -aai.edgeTag.proc.max.levels=50 - -# for transaction log -aai.logging.hbase.interceptor=true -aai.logging.hbase.enabled=true -aai.logging.hbase.logrequest=true -aai.logging.hbase.logresponse=true - -# for gremlin server -aai.server.rebind=g -hbase.table.name=<%= @TXN_HBASE_TABLE_NAME %> -hbase.table.timestamp.format=YYYYMMdd-HH:mm:ss:SSS -hbase.zookeeper.quorum=<%= @TXN_ZOOKEEPER_QUORUM %> -hbase.zookeeper.property.clientPort=<%= @TXN_ZOOKEEPER_PROPERTY_CLIENTPORT %> -hbase.zookeeper.znode.parent=<%= @TXN_HBASE_ZOOKEEPER_ZNODE_PARENT %> - -aai.logging.trace.enabled=true -aai.logging.trace.logrequest=false -aai.logging.trace.logresponse=false - - -aai.transaction.logging=true -aai.transaction.logging.get=false -aai.transaction.logging.post=false - -#limit set for bulk consumer APIS -aai.bulkconsumer.payloadlimit=30 - -#uncomment and use header X-OverrideLimit with the value to override the bulk api limit -#aai.bulkconsumer.payloadoverride=AAI-OVERRIDE-KEY -aai.bulkconsumer.payloadoverride=false - -#timeout for crud enabled flag -aai.crud.timeoutenabled=true - -#timeout app specific -aai.crud.timeout.appspecific=JUNITTESTAPP1,1|JUNITTESTAPP2,-1|DCAE-CCS,-1|DCAES,-1|AAI-FILEGEN-GFPIP,-1 - -#default timeout limit added for traversal if not overridden (in ms) -aai.crud.timeoutlimit=180000 \ No newline at end of file diff --git a/src/dkv/configurations/sampleAPPCConfig.properties b/src/dkv/configurations/sampleAPPCConfig.properties deleted file mode 100644 index 484337f..0000000 --- a/src/dkv/configurations/sampleAPPCConfig.properties +++ /dev/null @@ -1,113 +0,0 @@ -### ### -### Properties for demo ### -### ### -appc.demo.poolMembers=10.0.11.1:3904 -appc.demo.topic.read=APPC-CL -appc.demo.topic.write=APPC-CL -appc.demo.client.name=appcDemoEventListener -appc.demo.threads.queuesize.min=1 -appc.demo.threads.queuesize.max=1000 -appc.demo.threads.poolsize.min=1 -appc.demo.threads.poolsize.max=2 -appc.demo.provider.user=admin -appc.demo.provider.pass=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U -appc.demo.provider.url=http://localhost:8181/restconf/operations/appc-provider -appc.provider.vfodl.url=http://admin:Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U@10.0.2.1:8282/restconf/config/network-topology:network-topology/topology/topology-netconf/node/NODE_NAME/yang-ext:mount/sample-plugin:sample-plugin/pg-streams/ - -# The properties right below are needed to properly call the Master DG to serve demo purposes -appc.service.logic.module.name=APPC -appc.topology.dg.method=topology-operation-all -appc.topology.dg.version=2.0.0 - -# TEMP - Properties that might be needed to make the AAI-APPC connection -org.onap.appc.db.url.appcctl=jdbc:mysql://dbhost:3306/appcctl -org.onap.appc.db.user.appcctl=appcctl -org.onap.appc.db.pass.appcctl=appcctl - -org.onap.appc.db.url.sdnctl=jdbc:mysql://dbhost:3306/sdnctl -org.onap.appc.db.user.sdnctl=sdnctl -org.onap.appc.db.pass.sdnctl=gamma - - -### ### -### OpenStack credentials (these properties also are used in appc-rest-adapter-bundle, appc-chef-adapter-bundle, appc-iaas-adapter-bundle) ### -### ### -provider1.type=OpenStackProvider -provider1.name=OpenStack -provider1.identity=http://localhost:8181/apidoc/explorer/index.html -provider1.tenant1.name=default -provider1.tenant1.domain=default -provider1.tenant1.userid=admin -provider1.tenant1.password=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U - - - - - -### ### -### Properties that are not covered or being replaced from default.properties files. Default value for DMaaP IP is 10.0.11.1:3904 ### -### which is what the Master HEAT Template to instantiate ONAP is pointing to (version R1). All other default values are ### -### left there since these are pre-defined as part of APP-C/ONAP default instantiation with Master HEAT Template ### -### ### - - -# Property below is valid in appc-command-executor-core, appc-license-manager-core, appc-lifecycle-management-core, -# appc-request-handler-core, appc-workflow-management-core (all from the appc-dispatcher package). -dmaap.poolMembers=10.0.11.1:3904 - - -# appc-event-listener-bundle properties (only defined in src/test of default.properties) -appc.LCM.poolMembers=10.0.11.1:3904 -appc.LCM.topic.read=APPC-LCM-READ -appc.LCM.topic.write=APPC-LCM-WRITE -appc.LCM.client.name=APPC-EVENT-LISTENER-TEST -appc.LCM.provider.user=admin -appc.LCM.provider.pass=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U -appc.LCM.provider.url=http://localhost:8181/restconf/operations/appc-provider-lcm - - -# properties from appc-netconf-adapter-bundle, appc-dg-common, appc-dmaap-adapter-bundle -poolMembers=10.0.11.1:3904 -event.pool.members=10.0.11.1:3904 -restconf.user=admin -restconf.pass=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U - - -# properties found in appc-rest-adapter-bundle, appc-chef-adapter-bundle, appc-iaas-adapter-bundle) -#Your OpenStack IP -test.ip=10.0.11.100 -# Your OpenStack Platform's Keystone Port (default is 5000) -test.port=5000 -test.tenantid=test -test.vmid=test -# Port 8774 below is default port for OpenStack's Nova API Service -test.url=http://api.appc.local/vm/9999999/test/99999999-9999-9999-9999-999999999999 -#skips hypervisor check which usually occurs during iaas-adapter-bundle startup -org.onap.appc.iaas.skiphypervisorcheck=true - - -# Properties from default.properties in the src/test and src/main paths of appc-asdc-listener-bundle -appc.sdc.host=10.0.3.1:8443 -appc.sdc.env=APPC-ASDC-ENV -appc.sdc.user=test -appc.sdc.pass=test -appc.sdc.consumer=APPC-ASDC-CONSUMER -appc.sdc.consumer.id=APPC-ASDC-CONSUMER-ID -appc.sdc.provider.url=http://localhost:8181/restconf/operations/AsdcMessage:configuration-document-request - -# Properties used by EventSenderDmaapImpl.java -DCAE.dmaap.event.topic.write=EventSenderTest -DCAE.dmaap.appc.username=test -DCAE.dmaap.appc.password=test -DCAE.dmaap.event.pool.members=10.0.11.1:3904 - -# OAM Listener -appc.OAM.disabled=true -appc.OAM.provider.url=http://localhost:8181/restconf/operations/appc-oam -appc.OAM.poolMembers=10.0.11.1:3904 -appc.OAM.service=ueb -appc.OAM.topic.read=testOAM -appc.OAM.topic.write=testOAM -appc.OAM.client.name=testOAM -appc.OAM.provider.user=admin -appc.OAM.provider.pass=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U \ No newline at end of file diff --git a/src/dkv/main.go b/src/dkv/main.go index 8b4850f..5bf076f 100644 --- a/src/dkv/main.go +++ b/src/dkv/main.go @@ -31,10 +31,29 @@ func main() { log.Fatal(err) } router := mux.NewRouter() - router.HandleFunc("/loadconfigs", api.HandlePOST).Methods("POST") - router.HandleFunc("/getconfig/{key}", api.HandleGET).Methods("GET") - router.HandleFunc("/deleteconfig/{key}", api.HandleDELETE).Methods("DELETE") - router.HandleFunc("/getconfigs", api.HandleGETS).Methods("GET") + // Sevice Registration + // Domain CRUD + router.HandleFunc("/v1/register", api.HandleServiceCreate).Methods("POST") + router.HandleFunc("/v1/register/{token}", api.HandleServiceGet).Methods("GET") + router.HandleFunc("/v1/register/{token}", api.HandleServiceDelete).Methods("DELETE") + // Subdomain CRUD + router.HandleFunc("/v1/register/{token}/subdomain", api.HandleServiceSubdomainCreate).Methods("POST") + // router.HandleFunc("/v1/register/{token}/subdomain", api.HandleServiceSubdomainGet).Methods("GET") + router.HandleFunc("/v1/register/{token}/subdomain/{subdomain}", api.HandleServiceSubdomainDelete).Methods("DELETE") + // Configuration CRUD + router.HandleFunc("/v1/config", api.HandleConfigUpload).Methods("POST") + router.HandleFunc("/v1/config/{token}/{filename}", api.HandleConfigGet).Methods("GET") + router.HandleFunc("/v1/config/{token}/{subdomain}/{filename}", api.HandleConfigGet).Methods("GET") + router.HandleFunc("/v1/config/{token}/{filename}", api.HandleConfigDelete).Methods("DELETE") + router.HandleFunc("/v1/config/{token}/{subdomain}/{filename}", api.HandleConfigDelete).Methods("DELETE") + router.HandleFunc("/v1/config/load", api.HandleConfigLoad).Methods("POST") + // Load default configs + router.HandleFunc("/v1/config/load-default", api.HandleDefaultConfigLoad).Methods("GET") + // Direct Consul queries. + router.HandleFunc("/v1/getconfig/{token}/{key}", api.HandleGET).Methods("GET") + router.HandleFunc("/v1/deleteconfig/{key}", api.HandleDELETE).Methods("DELETE") + router.HandleFunc("/v1/getconfigs", api.HandleGETS).Methods("GET") + loggedRouter := handlers.LoggingHandler(os.Stdout, router) log.Println("[INFO] Started Distributed KV Store server.") log.Fatal(http.ListenAndServe(":8080", loggedRouter)) -- cgit 1.2.3-korg