SCB-1613 a new API for user want to list key values by labels (#53)
diff --git a/.travis.yml b/.travis.yml
index d5a358a..a425a01 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -58,4 +58,4 @@
- cd $HOME/gopath/src/github.com/apache/servicecomb-kie
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
- - bash scripts/travis/unit_test.sh
+ - bash scripts/travis/unit_test.sh && $HOME/gopath/bin/goveralls -coverprofile=coverage.txt -service=travis-ci
diff --git a/README.md b/README.md
index f3bf240..81f9c15 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,8 @@
-# Apache-ServiceComb-Kie [![Build Status](https://travis-ci.org/apache/servicecomb-kie.svg?branch=master)](https://travis-ci.org/apache/servicecomb-kie?branch=master) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
+# Apache-ServiceComb-Kie
+[![Build Status](https://travis-ci.org/apache/servicecomb-kie.svg?branch=master)](https://travis-ci.org/apache/servicecomb-kie?branch=master)
+[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
+[![Coverage Status](https://coveralls.io/repos/github/apache/servicecomb-kie/badge.svg?branch=master)](https://coveralls.io/github/apache/servicecomb-kie?branch=master)
A service for configuration management in distributed system.
## Conceptions
diff --git a/client/adaptor/kie_client.go b/client/adaptor/kie_client.go
index c2d0137..a640e8a 100644
--- a/client/adaptor/kie_client.go
+++ b/client/adaptor/kie_client.go
@@ -60,15 +60,15 @@
var err error
var configurationsValue []*model.KVResponse
if len(labels) != 0 {
- configurationsValue, err = c.KieClient.SearchByLabels(context.TODO(), client.WithGetProject("default"), client.WithLabels(labels...))
+ configurationsValue, err = c.KieClient.Search(context.TODO(), client.WithGetProject("default"), client.WithLabels(labels...))
} else {
- configurationsValue, err = c.KieClient.SearchByLabels(context.TODO(), client.WithGetProject("default"), client.WithLabels(c.opts.Labels))
+ configurationsValue, err = c.KieClient.Search(context.TODO(), client.WithGetProject("default"), client.WithLabels(c.opts.Labels))
}
if err != nil {
openlogging.GetLogger().Errorf("Error in Querying the Response from Kie %s %#v", err.Error(), labels)
return nil, err
}
- openlogging.GetLogger().Debugf("KieClient SearchByLabels. %#v", labels)
+ openlogging.GetLogger().Debugf("KieClient Search. %#v", labels)
//Parse config result.
for _, docRes := range configurationsValue {
for _, docInfo := range docRes.Data {
diff --git a/client/adaptor/kie_client_test.go b/client/adaptor/kie_client_test.go
index 9813512..cfd6954 100644
--- a/client/adaptor/kie_client_test.go
+++ b/client/adaptor/kie_client_test.go
@@ -138,7 +138,7 @@
config.LabelService: "test",
})
//assert.Equal(t, resp.StatusCode, 404)
- assert.Equal(t, err.Error(), "delete 1 failed,http status [200 OK], body [[{\"label\":null,\"data\":null}]]")
+ assert.Equal(t, "delete 1 failed,http status [200 OK], body [[{\"data\":null}]]", err.Error())
// Shutdown the helper server gracefully
if err := helper.Shutdown(context.Background()); err != nil {
panic(err)
diff --git a/client/client.go b/client/client.go
index 342038b..51bca74 100644
--- a/client/client.go
+++ b/client/client.go
@@ -158,8 +158,8 @@
return kvs, nil
}
-//SearchByLabels get value by lables
-func (c *Client) SearchByLabels(ctx context.Context, opts ...GetOption) ([]*model.KVResponse, error) {
+//Search get value by labels
+func (c *Client) Search(ctx context.Context, opts ...GetOption) ([]*model.KVResponse, error) {
options := GetOptions{}
for _, o := range opts {
o(&options)
diff --git a/go.mod b/go.mod
index 9a2103c..39f57e5 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@
github.com/go-chassis/go-archaius v0.24.0
github.com/go-chassis/go-chassis v1.7.6
github.com/go-chassis/go-chassis-config v0.15.0
- github.com/go-chassis/go-restful-swagger20 v1.0.2-0.20191118130439-7eec0f2639f6
+ github.com/go-chassis/go-restful-swagger20 v1.0.2-0.20191118130439-7eec0f2639f6 // indirect
github.com/go-chassis/paas-lager v1.0.2-0.20190328010332-cf506050ddb2
github.com/go-mesh/openlogging v1.0.1
github.com/golang/snappy v0.0.1 // indirect
diff --git a/pkg/model/kv.go b/pkg/model/kv.go
index 229ba7f..f603fe8 100644
--- a/pkg/model/kv.go
+++ b/pkg/model/kv.go
@@ -28,7 +28,10 @@
//KVResponse represents the key value list
type KVResponse struct {
- LabelDoc *LabelDocResponse `json:"label"`
+ LabelDoc *LabelDocResponse `json:"label,omitempty"`
+ PageNum int `json:"num,omitempty"`
+ Size int `json:"size,omitempty"`
+ Total int `json:"total,omitempty"`
Data []*KVDoc `json:"data"`
}
diff --git a/server/resource/v1/doc_struct.go b/server/resource/v1/doc_struct.go
index 2870376..b0afbb0 100644
--- a/server/resource/v1/doc_struct.go
+++ b/server/resource/v1/doc_struct.go
@@ -75,16 +75,19 @@
DataType: "string",
Name: "key",
ParamType: goRestful.PathParameterKind,
+ Required: true,
}
DocPathProject = &restful.Parameters{
DataType: "string",
Name: "project",
ParamType: goRestful.PathParameterKind,
+ Required: true,
}
DocPathLabelID = &restful.Parameters{
DataType: "string",
Name: "label_id",
ParamType: goRestful.PathParameterKind,
+ Required: true,
}
)
diff --git a/server/resource/v1/kv_resource.go b/server/resource/v1/kv_resource.go
index ee0db4a..b8e2010 100644
--- a/server/resource/v1/kv_resource.go
+++ b/server/resource/v1/kv_resource.go
@@ -20,6 +20,7 @@
import (
"net/http"
+ "strconv"
"github.com/apache/servicecomb-kie/pkg/common"
"github.com/apache/servicecomb-kie/pkg/model"
@@ -39,10 +40,6 @@
var err error
key := context.ReadPathParameter("key")
project := context.ReadPathParameter("project")
- if project == "" {
- WriteErrResponse(context, http.StatusForbidden, "project must not be empty", common.ContentTypeText)
- return
- }
kv := new(model.KVDoc)
if err = readRequest(context, kv); err != nil {
WriteErrResponse(context, http.StatusBadRequest, err.Error(), common.ContentTypeText)
@@ -78,10 +75,6 @@
return
}
project := context.ReadPathParameter("project")
- if project == "" {
- WriteErrResponse(context, http.StatusForbidden, "project must not be empty", common.ContentTypeText)
- return
- }
values := context.ReadRequest().URL.Query()
labels := make(map[string]string, len(values))
for k, v := range values {
@@ -118,8 +111,57 @@
}
-//SearchByLabels search key only by label
-func (r *KVResource) SearchByLabels(context *restful.Context) {
+//List TODO pagination
+func (r *KVResource) List(rctx *restful.Context) {
+ project := rctx.ReadPathParameter("project")
+ domain := ReadDomain(rctx)
+ if domain == nil {
+ WriteErrResponse(rctx, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
+ return
+ }
+ var limit int64 = 20
+ var offset int64 = 0
+ labels := make(map[string]string, 0)
+ var err error
+ for k, v := range rctx.ReadRequest().URL.Query() {
+ if k == "limit" {
+ limit, err = strconv.ParseInt(v[0], 10, 64)
+ if err != nil {
+ WriteErrResponse(rctx, http.StatusBadRequest, "invalid limit number", common.ContentTypeText)
+ }
+ if limit < 1 || limit > 50 {
+ WriteErrResponse(rctx, http.StatusBadRequest, "invalid limit number", common.ContentTypeText)
+ }
+ continue
+ }
+ if k == "offset" {
+ offset, err = strconv.ParseInt(v[0], 10, 64)
+ if err != nil {
+ WriteErrResponse(rctx, http.StatusBadRequest, "invalid offset number", common.ContentTypeText)
+ }
+ if offset < 1 {
+ WriteErrResponse(rctx, http.StatusBadRequest, "invalid offset number", common.ContentTypeText)
+ }
+ continue
+ }
+ labels[k] = v[0]
+ }
+ result, err := service.KVService.List(rctx.Ctx, domain.(string), project, "", labels, int(limit), int(offset))
+ if err != nil {
+ openlogging.Error("can not find by labels", openlogging.WithTags(openlogging.Tags{
+ "err": err.Error(),
+ }))
+ WriteErrResponse(rctx, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
+ return
+ }
+ err = writeResponse(rctx, result)
+ if err != nil {
+ openlogging.Error(err.Error())
+ }
+}
+
+//Search search key only by label
+func (r *KVResource) Search(context *restful.Context) {
var err error
labelCombinations, err := ReadLabelCombinations(context.ReadRestfulRequest())
if err != nil {
@@ -127,16 +169,23 @@
return
}
project := context.ReadPathParameter("project")
- if project == "" {
- WriteErrResponse(context, http.StatusForbidden, "project must not be empty", common.ContentTypeText)
- return
- }
domain := ReadDomain(context)
if domain == nil {
WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
return
}
var kvs []*model.KVResponse
+ if labelCombinations == nil {
+ result, err := service.KVService.FindKV(context.Ctx, domain.(string), project)
+ if err != nil {
+ openlogging.Error("can not find by labels", openlogging.WithTags(openlogging.Tags{
+ "err": err.Error(),
+ }))
+ WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
+ return
+ }
+ kvs = append(kvs, result...)
+ }
for _, labels := range labelCombinations {
openlogging.Debug("find by combination", openlogging.WithTags(openlogging.Tags{
"q": labels,
@@ -171,10 +220,6 @@
//Delete deletes key by ids
func (r *KVResource) Delete(context *restful.Context) {
project := context.ReadPathParameter("project")
- if project == "" {
- WriteErrResponse(context, http.StatusForbidden, "project must not be empty", common.ContentTypeText)
- return
- }
domain := ReadDomain(context)
if domain == nil {
WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
@@ -240,7 +285,7 @@
}, {
Method: http.MethodGet,
Path: "/v1/{project}/kie/kv",
- ResourceFunc: r.SearchByLabels,
+ ResourceFunc: r.Search,
FuncDesc: "search key values by labels combination",
Parameters: []*restful.Parameters{
DocPathProject, DocQueryCombination,
@@ -255,10 +300,26 @@
Consumes: []string{goRestful.MIME_JSON, common.ContentTypeYaml},
Produces: []string{goRestful.MIME_JSON, common.ContentTypeYaml},
}, {
+ Method: http.MethodGet,
+ Path: "/v1/{project}/kie/kv:list",
+ ResourceFunc: r.List,
+ FuncDesc: "list key values by labels and key",
+ Parameters: []*restful.Parameters{
+ DocPathProject, DocQueryLabelParameters,
+ },
+ Returns: []*restful.Returns{
+ {
+ Code: http.StatusOK,
+ Model: model.KVResponse{},
+ },
+ },
+ Consumes: []string{goRestful.MIME_JSON, common.ContentTypeYaml},
+ Produces: []string{goRestful.MIME_JSON, common.ContentTypeYaml},
+ }, {
Method: http.MethodDelete,
Path: "/v1/{project}/kie/kv",
ResourceFunc: r.Delete,
- FuncDesc: "delete key by kvID and labelID. if you want better performance, you need to give labelID",
+ FuncDesc: "delete key by kvID and labelID. Want better performance, give labelID",
Parameters: []*restful.Parameters{
DocPathProject,
DocQueryKVIDParameters,
diff --git a/server/resource/v1/kv_resource_test.go b/server/resource/v1/kv_resource_test.go
index c4c575e..5fe878b 100644
--- a/server/resource/v1/kv_resource_test.go
+++ b/server/resource/v1/kv_resource_test.go
@@ -22,6 +22,7 @@
"encoding/json"
"github.com/apache/servicecomb-kie/server/service"
"io/ioutil"
+ "log"
"net/http"
"net/http/httptest"
@@ -43,13 +44,12 @@
config.Configurations = &config.Config{
DB: config.DB{},
}
-
+ config.Configurations.DB.URI = "mongodb://kie:123@127.0.0.1:27017"
+ err := service.DBInit()
+ if err != nil {
+ panic(err)
+ }
Describe("put kv", func() {
- config.Configurations.DB.URI = "mongodb://kie:123@127.0.0.1:27017"
- err := service.DBInit()
- It("should not return err", func() {
- Expect(err).Should(BeNil())
- })
Context("valid param", func() {
kv := &model.KVDoc{
Key: "timeout",
@@ -86,4 +86,34 @@
})
})
})
+ Describe("list kv", func() {
+ Context("with no label", func() {
+ r, _ := http.NewRequest("GET", "/v1/test/kie/kv:list", nil)
+ noopH := &noop.NoopAuthHandler{}
+ chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
+ r.Header.Set("Content-Type", "application/json")
+ kvr := &v1.KVResource{}
+ c, err := restfultest.New(kvr, chain)
+ It("should not return error", func() {
+ Expect(err).Should(BeNil())
+ })
+ resp := httptest.NewRecorder()
+ c.ServeHTTP(resp, r)
+
+ body, err := ioutil.ReadAll(resp.Body)
+ It("should not return err", func() {
+ Expect(err).Should(BeNil())
+ })
+ log.Println(string(body))
+ result := &model.KVResponse{}
+ err = json.Unmarshal(body, result)
+ It("should not return err", func() {
+ Expect(err).Should(BeNil())
+ })
+
+ It("should longer than 1", func() {
+ Expect(len(result.Data)).NotTo(Equal(0))
+ })
+ })
+ })
})
diff --git a/server/service/mongo/kv/kv_service.go b/server/service/mongo/kv/kv_service.go
index e93eaa8..6b2e86e 100644
--- a/server/service/mongo/kv/kv_service.go
+++ b/server/service/mongo/kv/kv_service.go
@@ -192,6 +192,30 @@
return nil
}
+//List get kv list by key and criteria
+func (s *Service) List(ctx context.Context, domain, project, key string, labels map[string]string, limit, offset int) (*model.KVResponse, error) {
+ opts := service.NewDefaultFindOpts()
+ opts.Labels = labels
+ opts.Key = key
+ cur, err := findKV(ctx, domain, project, opts)
+ if err != nil {
+ return nil, err
+ }
+ defer cur.Close(ctx)
+ result := &model.KVResponse{}
+ for cur.Next(ctx) {
+ curKV := &model.KVDoc{}
+
+ if err := cur.Decode(curKV); err != nil {
+ openlogging.Error("decode to KVs error: " + err.Error())
+ return nil, err
+ }
+ clearPart(curKV)
+ result.Data = append(result.Data, curKV)
+ }
+ return result, nil
+}
+
//FindKV get kvs by key, labels
//because labels has a a lot of combination,
//you can use WithDepth(0) to return only one kv which's labels exactly match the criteria
@@ -251,7 +275,7 @@
for _, labelGroup = range kvResp {
if reflect.DeepEqual(labelGroup.LabelDoc.Labels, curKV.Labels) {
groupExist = true
- clearKV(curKV)
+ clearAll(curKV)
labelGroup.Data = append(labelGroup.Data, curKV)
break
}
@@ -265,7 +289,7 @@
},
Data: []*model.KVDoc{curKV},
}
- clearKV(curKV)
+ clearAll(curKV)
openlogging.Debug("add new label group")
kvResp = append(kvResp, labelGroup)
}
diff --git a/server/service/mongo/kv/tool.go b/server/service/mongo/kv/tool.go
index cbf40ec..c1a3f47 100644
--- a/server/service/mongo/kv/tool.go
+++ b/server/service/mongo/kv/tool.go
@@ -26,16 +26,18 @@
"go.mongodb.org/mongo-driver/mongo"
)
-//clearKV clean attr which don't need to return to client side
-func clearKV(kv *model.KVDoc) {
- kv.Domain = ""
+//clearAll clean attr which don't need to return to client side
+func clearAll(kv *model.KVDoc) {
+ clearPart(kv)
kv.Labels = nil
kv.LabelID = ""
+}
+func clearPart(kv *model.KVDoc) {
+ kv.Domain = ""
kv.Project = ""
}
-
func cursorToOneKV(ctx context.Context, cur *mongo.Cursor, labels map[string]string) ([]*model.KVResponse, error) {
- kvResp := make([]*model.KVResponse, 0)
+ result := make([]*model.KVResponse, 0)
curKV := &model.KVDoc{} //reuse this pointer to reduce GC, only clear label
//check label length to get the exact match
for cur.Next(ctx) { //although complexity is O(n), but there won't be so much labels for one key
@@ -57,10 +59,10 @@
},
Data: make([]*model.KVDoc, 0),
}
- clearKV(curKV)
+ clearAll(curKV)
labelGroup.Data = append(labelGroup.Data, curKV)
- kvResp = append(kvResp, labelGroup)
- return kvResp, nil
+ result = append(result, labelGroup)
+ return result, nil
}
}
diff --git a/server/service/options.go b/server/service/options.go
index e8eb5cb..9fe41c6 100644
--- a/server/service/options.go
+++ b/server/service/options.go
@@ -17,7 +17,17 @@
package service
-import "time"
+import (
+ "github.com/apache/servicecomb-kie/server/service/mongo/session"
+ "time"
+)
+
+//NewDefaultFindOpts return default options
+func NewDefaultFindOpts() FindOptions {
+ return FindOptions{
+ Timeout: session.DefaultTimeout,
+ }
+}
//FindOptions is option to find key value
type FindOptions struct {
diff --git a/server/service/service.go b/server/service/service.go
index 04310f6..5d067a2 100644
--- a/server/service/service.go
+++ b/server/service/service.go
@@ -38,8 +38,11 @@
//KV provide api of KV entity
type KV interface {
+ //below 3 methods is usually for admin console
CreateOrUpdate(ctx context.Context, kv *model.KVDoc) (*model.KVDoc, error)
+ List(ctx context.Context, domain, project, key string, labels map[string]string, limit, offset int) (*model.KVResponse, error)
Delete(kvID string, labelID string, domain, project string) error
+ //FindKV is usually for service to pull configs
FindKV(ctx context.Context, domain, project string, options ...FindOption) ([]*model.KVResponse, error)
}