You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by ti...@apache.org on 2020/04/13 01:22:51 UTC
[servicecomb-kie] branch master updated: support delete multiple
kvs (#135)
This is an automated email from the ASF dual-hosted git repository.
tianxiaoliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-kie.git
The following commit(s) were added to refs/heads/master by this push:
new fa6c3b2 support delete multiple kvs (#135)
fa6c3b2 is described below
commit fa6c3b26b30686465f323f62fab653093037bf05
Author: zhulijian <zh...@huawei.com>
AuthorDate: Mon Apr 13 09:22:40 2020 +0800
support delete multiple kvs (#135)
* value do not omitempty
* support delete multiple kvs
---
docs/api.yaml | 35 +++++++++++
pkg/model/db_schema.go | 2 +-
server/resource/v1/common.go | 6 ++
server/resource/v1/doc_struct.go | 14 ++++-
server/resource/v1/history_resource_test.go | 12 ++--
server/resource/v1/kv_resource.go | 90 ++++++++++++++++++++++-----
server/resource/v1/kv_resource_test.go | 94 +++++++++++++++++++++++------
server/resource/v1/label_resouce.go | 6 +-
server/resource/v1/label_resouce_test.go | 8 +--
server/service/mongo/history/dao.go | 7 ++-
server/service/mongo/kv/kv_dao.go | 66 +++++++++++++++-----
server/service/mongo/kv/kv_service.go | 26 ++++----
server/service/mongo/kv/kv_test.go | 2 +-
server/service/service.go | 5 +-
14 files changed, 292 insertions(+), 81 deletions(-)
diff --git a/docs/api.yaml b/docs/api.yaml
index c45264b..2c70d55 100644
--- a/docs/api.yaml
+++ b/docs/api.yaml
@@ -94,6 +94,34 @@ paths:
description: ""
schema:
$ref: '#/definitions/DocResponseSingleKey'
+ delete:
+ summary: delete keys.
+ operationId: DeleteList
+ parameters:
+ - name: project
+ in: path
+ required: true
+ type: string
+ - name: Content-Type
+ in: header
+ description: used to indicate the media type of the resource, the value only
+ can be application/json
+ required: true
+ type: string
+ - name: body
+ in: body
+ required: true
+ schema:
+ $ref: '#/definitions/v1.DeleteBody'
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ responses:
+ "204":
+ description: delete success
+ "500":
+ description: server error
/v1/{project}/kie/kv/{kv_id}:
get:
summary: get key values by kv_id
@@ -344,6 +372,13 @@ definitions:
type: string
user_agent:
type: string
+ v1.DeleteBody:
+ type: object
+ properties:
+ ids:
+ type: array
+ items:
+ type: string
v1.KVCreateBody:
type: object
properties:
diff --git a/pkg/model/db_schema.go b/pkg/model/db_schema.go
index da6b496..d28a191 100644
--- a/pkg/model/db_schema.go
+++ b/pkg/model/db_schema.go
@@ -32,7 +32,7 @@ type KVDoc struct {
ID string `json:"id,omitempty" bson:"id,omitempty" yaml:"id,omitempty" swag:"string"`
LabelID string `json:"label_id,omitempty" bson:"label_id,omitempty" yaml:"label_id,omitempty"`
Key string `json:"key" yaml:"key" validate:"commonName"`
- Value string `json:"value,omitempty" yaml:"value,omitempty" validate:"ascii,min=1,max=2097152"`
+ Value string `json:"value" yaml:"value" validate:"ascii,min=1,max=2097152"`
ValueType string `json:"value_type,omitempty" bson:"value_type,omitempty" yaml:"value_type,omitempty" validate:"valueType"` //ini,json,text,yaml,properties
Checker string `json:"check,omitempty" yaml:"check,omitempty"` //python script
CreateRevision int64 `json:"create_revision,omitempty" bson:"create_revision," yaml:"create_revision,omitempty"`
diff --git a/server/resource/v1/common.go b/server/resource/v1/common.go
index 0770109..19856e9 100644
--- a/server/resource/v1/common.go
+++ b/server/resource/v1/common.go
@@ -43,6 +43,8 @@ const (
HeaderUserAgent = "User-Agent"
HeaderSessionID = "X-Session-Id"
AttributeDomainKey = "domain"
+
+ FmtReadRequestError = "decode request body failed: %v"
)
//err
@@ -233,6 +235,10 @@ func validateDelete(domain, project, kvID string) error {
return validateGet(domain, project, kvID)
}
+func validateDeleteList(domain, project string) error {
+ return checkDomainAndProject(domain, project)
+}
+
func checkDomainAndProject(domain, project string) error {
if domain == "" {
return session.ErrMissingDomain
diff --git a/server/resource/v1/doc_struct.go b/server/resource/v1/doc_struct.go
index 63f2613..c4aef61 100644
--- a/server/resource/v1/doc_struct.go
+++ b/server/resource/v1/doc_struct.go
@@ -32,13 +32,20 @@ var (
ParamType: goRestful.HeaderParameterKind,
Desc: "integer, default is 1, if you set match policy, you can set,depth to decide label number",
}
- DocHeaderContentType = &restful.Parameters{
+ DocHeaderContentTypeJSONAndYaml = &restful.Parameters{
DataType: "string",
Name: common.HeaderContentType,
ParamType: goRestful.HeaderParameterKind,
Required: true,
Desc: "used to indicate the media type of the resource, the value can be application/json or text/yaml",
}
+ DocHeaderContentTypeJSON = &restful.Parameters{
+ DataType: "string",
+ Name: common.HeaderContentType,
+ ParamType: goRestful.HeaderParameterKind,
+ Required: true,
+ Desc: "used to indicate the media type of the resource, the value only can be application/json",
+ }
)
//swagger doc response header params
@@ -169,6 +176,11 @@ type KVUpdateBody struct {
Value string `json:"value"`
}
+//DeleteBody is the request body struct of delete multiple kvs interface
+type DeleteBody struct {
+ IDs []string `json:"ids"`
+}
+
//ErrorMsg is open api doc
type ErrorMsg struct {
Msg string `json:"error_msg"`
diff --git a/server/resource/v1/history_resource_test.go b/server/resource/v1/history_resource_test.go
index e46ab06..84e84a8 100644
--- a/server/resource/v1/history_resource_test.go
+++ b/server/resource/v1/history_resource_test.go
@@ -45,10 +45,10 @@ func TestHistoryResource_GetRevisions(t *testing.T) {
"test": "revisions",
},
Domain: "default",
- Project: "test",
+ Project: "history_test",
}
kv, _ = service.KVService.CreateOrUpdate(context.Background(), kv)
- path := fmt.Sprintf("/v1/test/kie/revision/%s", kv.ID)
+ path := fmt.Sprintf("/v1/history_test/kie/revision/%s", kv.ID)
r, _ := http.NewRequest("GET", path, nil)
revision := &v1.HistoryResource{}
chain, _ := handler.GetChain(common.Provider, "")
@@ -66,10 +66,10 @@ func TestHistoryResource_GetRevisions(t *testing.T) {
t.Run("put again, should has 2 revision", func(t *testing.T) {
kv.Domain = "default"
- kv.Project = "test"
+ kv.Project = "history_test"
kv, err = service.KVService.CreateOrUpdate(context.Background(), kv)
assert.NoError(t, err)
- path := fmt.Sprintf("/v1/test/kie/revision/%s", kv.ID)
+ path := fmt.Sprintf("/v1/history_test/kie/revision/%s", kv.ID)
r, _ := http.NewRequest("GET", path, nil)
revision := &v1.HistoryResource{}
chain, _ := handler.GetChain(common.Provider, "")
@@ -88,7 +88,7 @@ func TestHistoryResource_GetRevisions(t *testing.T) {
func TestHistoryResource_GetPollingData(t *testing.T) {
t.Run("list kv by service label, to create a polling data", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv", nil)
+ r, _ := http.NewRequest("GET", "/v1/history_test/kie/kv", nil)
noopH := &handler2.NoopAuthHandler{}
trackH := &handler2.TrackHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain3", noopH.Name(), trackH.Name())
@@ -106,7 +106,7 @@ func TestHistoryResource_GetPollingData(t *testing.T) {
assert.NoError(t, err)
})
t.Run("get polling data", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/track?sessionId=test", nil)
+ r, _ := http.NewRequest("GET", "/v1/history_test/kie/track?sessionId=test", nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain3", noopH.Name())
r.Header.Set("Content-Type", "application/json")
diff --git a/server/resource/v1/kv_resource.go b/server/resource/v1/kv_resource.go
index ac582e8..64cb170 100644
--- a/server/resource/v1/kv_resource.go
+++ b/server/resource/v1/kv_resource.go
@@ -20,6 +20,7 @@ package v1
import (
"context"
+ "encoding/json"
"fmt"
"github.com/go-chassis/go-chassis/pkg/backends/quota"
"net/http"
@@ -45,7 +46,7 @@ func (r *KVResource) Post(rctx *restful.Context) {
project := rctx.ReadPathParameter(common.PathParameterProject)
kv := new(model.KVDoc)
if err = readRequest(rctx, kv); err != nil {
- WriteErrResponse(rctx, http.StatusBadRequest, err.Error())
+ WriteErrResponse(rctx, http.StatusBadRequest, fmt.Sprintf(FmtReadRequestError, err))
return
}
domain := ReadDomain(rctx)
@@ -103,7 +104,7 @@ func (r *KVResource) Put(rctx *restful.Context) {
project := rctx.ReadPathParameter(common.PathParameterProject)
kv := new(model.KVDoc)
if err = readRequest(rctx, kv); err != nil {
- WriteErrResponse(rctx, http.StatusBadRequest, err.Error())
+ WriteErrResponse(rctx, http.StatusBadRequest, fmt.Sprintf(FmtReadRequestError, err))
return
}
domain := ReadDomain(rctx)
@@ -268,7 +269,7 @@ func returnData(rctx *restful.Context, doc *model.KVDoc, offset, limit int64, se
}
}
-//Delete deletes key by ids
+//Delete deletes one kv by id
func (r *KVResource) Delete(rctx *restful.Context) {
project := rctx.ReadPathParameter(common.PathParameterProject)
domain := ReadDomain(rctx).(string)
@@ -278,9 +279,12 @@ func (r *KVResource) Delete(rctx *restful.Context) {
WriteErrResponse(rctx, http.StatusBadRequest, err.Error())
return
}
- kv, err := service.KVService.Get(rctx.Ctx, domain, project, kvID)
+ kv, err := service.KVService.FindOneAndDelete(rctx.Ctx, kvID, domain, project)
if err != nil {
- openlogging.Error("kv_resource: " + err.Error())
+ openlogging.Error("delete failed, ", openlogging.WithTags(openlogging.Tags{
+ "kvID": kvID,
+ "error": err.Error(),
+ }))
if err == service.ErrKeyNotExists {
WriteErrResponse(rctx, http.StatusNotFound, err.Error())
return
@@ -288,15 +292,6 @@ func (r *KVResource) Delete(rctx *restful.Context) {
WriteErrResponse(rctx, http.StatusInternalServerError, common.MsgDeleteKVFailed)
return
}
- err = service.KVService.Delete(rctx.Ctx, kvID, domain, project)
- if err != nil {
- openlogging.Error("delete failed ,", openlogging.WithTags(openlogging.Tags{
- "kvID": kvID,
- "error": err.Error(),
- }))
- WriteErrResponse(rctx, http.StatusInternalServerError, common.MsgDeleteKVFailed)
- return
- }
err = pubsub.Publish(&pubsub.KVChangeEvent{
Key: kv.Key,
Labels: kv.Labels,
@@ -310,6 +305,48 @@ func (r *KVResource) Delete(rctx *restful.Context) {
rctx.WriteHeader(http.StatusNoContent)
}
+//DeleteList deletes multiple kvs by ids
+func (r *KVResource) DeleteList(rctx *restful.Context) {
+ project := rctx.ReadPathParameter(common.PathParameterProject)
+ domain := ReadDomain(rctx).(string)
+ b := new(DeleteBody)
+ if err := json.NewDecoder(rctx.ReadRequest().Body).Decode(b); err != nil {
+ WriteErrResponse(rctx, http.StatusBadRequest, fmt.Sprintf(FmtReadRequestError, err))
+ return
+ }
+ err := validateDeleteList(domain, project)
+ if err != nil {
+ WriteErrResponse(rctx, http.StatusBadRequest, err.Error())
+ return
+ }
+ kvs, err := service.KVService.FindManyAndDelete(rctx.Ctx, b.IDs, domain, project)
+ if err != nil {
+ if err == service.ErrKeyNotExists {
+ rctx.WriteHeader(http.StatusNoContent)
+ return
+ }
+ openlogging.Error("delete list failed, ", openlogging.WithTags(openlogging.Tags{
+ "kvIDs": b.IDs,
+ "error": err.Error(),
+ }))
+ WriteErrResponse(rctx, http.StatusInternalServerError, common.MsgDeleteKVFailed)
+ return
+ }
+ for _, kv := range kvs {
+ err = pubsub.Publish(&pubsub.KVChangeEvent{
+ Key: kv.Key,
+ Labels: kv.Labels,
+ Project: project,
+ DomainID: domain,
+ Action: pubsub.ActionDelete,
+ })
+ if err != nil {
+ openlogging.Warn("lost kv change event:" + err.Error())
+ }
+ }
+ rctx.WriteHeader(http.StatusNoContent)
+}
+
//URLPatterns defined config operations
func (r *KVResource) URLPatterns() []restful.Route {
return []restful.Route{
@@ -319,7 +356,7 @@ func (r *KVResource) URLPatterns() []restful.Route {
ResourceFunc: r.Post,
FuncDesc: "create a key value",
Parameters: []*restful.Parameters{
- DocPathProject, DocHeaderContentType,
+ DocPathProject, DocHeaderContentTypeJSONAndYaml,
},
Read: KVCreateBody{},
Returns: []*restful.Returns{
@@ -336,7 +373,7 @@ func (r *KVResource) URLPatterns() []restful.Route {
ResourceFunc: r.Put,
FuncDesc: "update a key value",
Parameters: []*restful.Parameters{
- DocPathProject, DocPathKeyID, DocHeaderContentType,
+ DocPathProject, DocPathKeyID, DocHeaderContentTypeJSONAndYaml,
},
Read: KVUpdateBody{},
Returns: []*restful.Returns{
@@ -415,6 +452,27 @@ func (r *KVResource) URLPatterns() []restful.Route {
Message: "server error",
},
},
+ }, {
+ Method: http.MethodDelete,
+ Path: "/v1/{project}/kie/kv",
+ ResourceFunc: r.DeleteList,
+ FuncDesc: "delete keys.",
+ Parameters: []*restful.Parameters{
+ DocPathProject, DocHeaderContentTypeJSON,
+ },
+ Read: DeleteBody{},
+ Returns: []*restful.Returns{
+ {
+ Code: http.StatusNoContent,
+ Message: "delete success",
+ },
+ {
+ Code: http.StatusInternalServerError,
+ Message: "server error",
+ },
+ },
+ Consumes: []string{goRestful.MIME_JSON},
+ Produces: []string{goRestful.MIME_JSON},
},
}
}
diff --git a/server/resource/v1/kv_resource_test.go b/server/resource/v1/kv_resource_test.go
index 82a27ef..5868e6a 100644
--- a/server/resource/v1/kv_resource_test.go
+++ b/server/resource/v1/kv_resource_test.go
@@ -92,7 +92,7 @@ func TestKVResource_Post(t *testing.T) {
Labels: map[string]string{"service": "utService"},
}
j, _ := json.Marshal(kv)
- r, _ := http.NewRequest("POST", "/v1/test/kie/kv", bytes.NewBuffer(j))
+ r, _ := http.NewRequest("POST", "/v1/kv_test/kie/kv", bytes.NewBuffer(j))
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -117,7 +117,7 @@ func TestKVResource_Post(t *testing.T) {
Labels: map[string]string{"service": "utService"},
}
j, _ := json.Marshal(kv)
- r, _ := http.NewRequest("POST", "/v1/test/kie/kv", bytes.NewBuffer(j))
+ r, _ := http.NewRequest("POST", "/v1/kv_test/kie/kv", bytes.NewBuffer(j))
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -144,7 +144,7 @@ func TestKVResource_Post(t *testing.T) {
"version": "1.0.0"},
}
j, _ := json.Marshal(kv)
- r, _ := http.NewRequest("POST", "/v1/test/kie/kv", bytes.NewBuffer(j))
+ r, _ := http.NewRequest("POST", "/v1/kv_test/kie/kv", bytes.NewBuffer(j))
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -163,7 +163,7 @@ func TestKVResource_Post(t *testing.T) {
}
func TestKVResource_List(t *testing.T) {
t.Run("list kv by service label, should return 3 kvs", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=service:utService", nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?label=service:utService", nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -182,7 +182,7 @@ func TestKVResource_List(t *testing.T) {
})
var rev string
t.Run("list kv by service label, exact match,should return 2 kv", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=service:utService&match=exact", nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?label=service:utService&match=exact", nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -200,7 +200,7 @@ func TestKVResource_List(t *testing.T) {
rev = resp.Header().Get(common2.HeaderRevision)
})
t.Run("list kv by service label, with current rev param,should return 304 ", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=service:utService&"+common2.QueryParamRev+"="+rev, nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?label=service:utService&"+common2.QueryParamRev+"="+rev, nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -213,7 +213,7 @@ func TestKVResource_List(t *testing.T) {
assert.Equal(t, http.StatusNotModified, resp.Result().StatusCode)
})
t.Run("list kv by service label, with old rev param,should return latest revision", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=service:utService&"+common2.QueryParamRev+"=1", nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?label=service:utService&"+common2.QueryParamRev+"=1", nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -226,7 +226,7 @@ func TestKVResource_List(t *testing.T) {
assert.Equal(t, http.StatusOK, resp.Result().StatusCode)
})
t.Run("list kv by service label, with wait and old rev param,should return latest revision,no wait", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=service:utService&wait=1s&"+common2.QueryParamRev+"=1", nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?label=service:utService&wait=1s&"+common2.QueryParamRev+"=1", nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -241,7 +241,7 @@ func TestKVResource_List(t *testing.T) {
assert.Equal(t, http.StatusOK, resp.Result().StatusCode)
})
t.Run("list kv by service label, with wait and current rev param,should wait and return 304 ", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=service:utService&wait=1s&"+common2.QueryParamRev+"="+rev, nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?label=service:utService&wait=1s&"+common2.QueryParamRev+"="+rev, nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -256,7 +256,7 @@ func TestKVResource_List(t *testing.T) {
assert.Equal(t, http.StatusNotModified, resp.Result().StatusCode)
})
t.Run("list kv by service label, with wait param,will exceed 1s and return 304", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=service:utService&wait=1s", nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?label=service:utService&wait=1s", nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -270,7 +270,7 @@ func TestKVResource_List(t *testing.T) {
t.Log(duration)
})
t.Run("list kv by service label offset, should return 1kv", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=service:utService&offset=1&limit=1", nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?label=service:utService&offset=1&limit=1", nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -287,7 +287,7 @@ func TestKVResource_List(t *testing.T) {
assert.Equal(t, 1, len(result.Data))
})
t.Run("list kv by service label, with wait and match param,not exact match and return 304", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?label=match:test&wait=10s&match=exact", nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?label=match:test&wait=10s&match=exact", nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain-match", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -304,7 +304,7 @@ func TestKVResource_List(t *testing.T) {
Labels: map[string]string{"dummy": "test", "match": "test"},
}
j, _ := json.Marshal(kv)
- r2, _ := http.NewRequest("POST", "/v1/test/kie/kv", bytes.NewBuffer(j))
+ r2, _ := http.NewRequest("POST", "/v1/kv_test/kie/kv", bytes.NewBuffer(j))
noopH2 := &handler2.NoopAuthHandler{}
chain2, _ := handler.CreateChain(common.Provider, "testchain-match", noopH2.Name())
r2.Header.Set("Content-Type", "application/json")
@@ -329,7 +329,7 @@ func TestKVResource_List(t *testing.T) {
t.Log(duration)
})
t.Run("get one key by label, exact match,should return 1 kv", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?key=timeout&label=service:utService&match=exact", nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?key=timeout&label=service:utService&match=exact", nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -346,7 +346,7 @@ func TestKVResource_List(t *testing.T) {
assert.Equal(t, 1, len(result.Data))
})
t.Run("get one key by service label should return 2 kv,delete one", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv?key=timeout&label=service:utService", nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv?key=timeout&label=service:utService", nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -362,7 +362,7 @@ func TestKVResource_List(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 2, len(result.Data))
- r2, _ := http.NewRequest("DELETE", "/v1/test/kie/kv/"+result.Data[0].ID, nil)
+ r2, _ := http.NewRequest("DELETE", "/v1/kv_test/kie/kv/"+result.Data[0].ID, nil)
c2, err := restfultest.New(kvr, chain)
assert.NoError(t, err)
resp2 := httptest.NewRecorder()
@@ -380,7 +380,7 @@ func TestKVResource_PutAndGet(t *testing.T) {
}
t.Run("create a kv, the value of user is guest", func(t *testing.T) {
j, _ := json.Marshal(kv)
- r, _ := http.NewRequest("POST", "/v1/test/kie/kv", bytes.NewBuffer(j))
+ r, _ := http.NewRequest("POST", "/v1/kv_test/kie/kv", bytes.NewBuffer(j))
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -399,7 +399,7 @@ func TestKVResource_PutAndGet(t *testing.T) {
id = result.ID
})
t.Run("get one key by kv_id", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv/"+id, nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv/"+id, nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -420,7 +420,7 @@ func TestKVResource_PutAndGet(t *testing.T) {
}
t.Run("update the kv, set the value of user to admin", func(t *testing.T) {
j, _ := json.Marshal(kvUpdate)
- r, _ := http.NewRequest("PUT", "/v1/test/kie/kv/"+id, bytes.NewBuffer(j))
+ r, _ := http.NewRequest("PUT", "/v1/kv_test/kie/kv/"+id, bytes.NewBuffer(j))
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -437,7 +437,7 @@ func TestKVResource_PutAndGet(t *testing.T) {
assert.Equal(t, kvUpdate.Value, result.Value)
})
t.Run("get one key by kv_id again", func(t *testing.T) {
- r, _ := http.NewRequest("GET", "/v1/test/kie/kv/"+id, nil)
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv/"+id, nil)
noopH := &handler2.NoopAuthHandler{}
chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
r.Header.Set("Content-Type", "application/json")
@@ -472,3 +472,57 @@ func TestKVResource_PutAndGet(t *testing.T) {
assert.Equal(t, http.StatusUnprocessableEntity, resp.Code)
})
}
+func TestKVResource_DeleteList(t *testing.T) {
+ var ids []string
+ t.Run("get ids of all kvs", func(t *testing.T) {
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv", nil)
+ noopH := &handler2.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)
+ assert.NoError(t, err)
+ resp := httptest.NewRecorder()
+ c.ServeHTTP(resp, r)
+ body, err := ioutil.ReadAll(resp.Body)
+ assert.NoError(t, err)
+ result := &model.KVResponse{}
+ err = json.Unmarshal(body, result)
+ assert.NoError(t, err)
+ assert.NotEqual(t, 0, len(result.Data))
+ for _, kv := range result.Data {
+ ids = append(ids, kv.ID)
+ }
+ })
+ t.Run("delete all kvs by ids", func(t *testing.T) {
+ j, _ := json.Marshal(v1.DeleteBody{IDs: ids})
+ r, _ := http.NewRequest("DELETE", "/v1/kv_test/kie/kv", bytes.NewBuffer(j))
+ noopH := &handler2.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)
+ assert.NoError(t, err)
+ resp := httptest.NewRecorder()
+ c.ServeHTTP(resp, r)
+ assert.Equal(t, http.StatusNoContent, resp.Code)
+
+ })
+ t.Run("get all kvs again, should return 0 kv", func(t *testing.T) {
+ r, _ := http.NewRequest("GET", "/v1/kv_test/kie/kv", nil)
+ noopH := &handler2.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)
+ assert.NoError(t, err)
+ resp := httptest.NewRecorder()
+ c.ServeHTTP(resp, r)
+ body, err := ioutil.ReadAll(resp.Body)
+ assert.NoError(t, err)
+ result := &model.KVResponse{}
+ err = json.Unmarshal(body, result)
+ assert.NoError(t, err)
+ assert.Equal(t, 0, len(result.Data))
+ })
+}
diff --git a/server/resource/v1/label_resouce.go b/server/resource/v1/label_resouce.go
index d7d688d..23905bf 100644
--- a/server/resource/v1/label_resouce.go
+++ b/server/resource/v1/label_resouce.go
@@ -1,13 +1,15 @@
package v1
import (
+ "fmt"
+ "net/http"
+
"github.com/apache/servicecomb-kie/pkg/common"
"github.com/apache/servicecomb-kie/pkg/model"
"github.com/apache/servicecomb-kie/server/service"
goRestful "github.com/emicklei/go-restful"
"github.com/go-chassis/go-chassis/server/restful"
"github.com/go-mesh/openlogging"
- "net/http"
)
//LabelResource is label API
@@ -20,7 +22,7 @@ func (r *LabelResource) PutLabel(context *restful.Context) {
var err error
entity := new(model.LabelDoc)
if err = readRequest(context, entity); err != nil {
- WriteErrResponse(context, http.StatusBadRequest, err.Error())
+ WriteErrResponse(context, http.StatusBadRequest, fmt.Sprintf(FmtReadRequestError, err))
return
}
entity.Project = context.ReadPathParameter("project")
diff --git a/server/resource/v1/label_resouce_test.go b/server/resource/v1/label_resouce_test.go
index 6f7a567..411197d 100644
--- a/server/resource/v1/label_resouce_test.go
+++ b/server/resource/v1/label_resouce_test.go
@@ -27,11 +27,11 @@ func TestLabelResource_PutLabel(t *testing.T) {
"test": "revisions",
},
Domain: "default",
- Project: "test",
+ Project: "label_test",
}
kv, _ = service.KVService.CreateOrUpdate(context.Background(), kv)
j := []byte("{\"alias\":\"test\",\"id\":\"" + kv.LabelID + "\"}")
- r, _ := http.NewRequest("PUT", "/v1/test/kie/label", bytes.NewBuffer(j))
+ r, _ := http.NewRequest("PUT", "/v1/label_test/kie/label", bytes.NewBuffer(j))
r.Header.Add("Content-Type", "application/json")
revision := &v1.LabelResource{}
noopH := &handler2.NoopAuthHandler{}
@@ -53,10 +53,10 @@ func TestLabelResource_PutLabel(t *testing.T) {
"test": "revisions",
},
Domain: "default",
- Project: "test",
+ Project: "label_test",
}
j, _ := json.Marshal(label)
- r, _ := http.NewRequest("PUT", "/v1/test/kie/label", bytes.NewBuffer(j))
+ r, _ := http.NewRequest("PUT", "/v1/label_test/kie/label", bytes.NewBuffer(j))
r.Header.Add("Content-Type", "application/json")
revision := &v1.LabelResource{}
noopH := &handler2.NoopAuthHandler{}
diff --git a/server/service/mongo/history/dao.go b/server/service/mongo/history/dao.go
index 486aedb..b769f09 100644
--- a/server/service/mongo/history/dao.go
+++ b/server/service/mongo/history/dao.go
@@ -96,10 +96,11 @@ func AddHistory(ctx context.Context, kv *model.KVDoc) error {
//AddDeleteTime add delete time to all revisions of the kv,
//thus these revisions will be automatically deleted by TTL index.
-func AddDeleteTime(ctx context.Context, kvID, project, domain string) error {
+func AddDeleteTime(ctx context.Context, kvIDs []string, project, domain string) error {
collection := session.GetDB().Collection(session.CollectionKVRevision)
now := time.Now()
- _, err := collection.UpdateMany(ctx, bson.M{"id": kvID, "project": project, "domain": domain}, bson.D{
+ filter := bson.D{{"id", bson.M{"$in": kvIDs}}, {"project", project}, {"domain", domain}}
+ _, err := collection.UpdateMany(ctx, filter, bson.D{
{"$set", bson.D{
{"delete_time", now},
}},
@@ -107,7 +108,7 @@ func AddDeleteTime(ctx context.Context, kvID, project, domain string) error {
if err != nil {
return err
}
- openlogging.Debug(fmt.Sprintf("added delete time [%s] to key [%s]", now.String(), kvID))
+ openlogging.Debug(fmt.Sprintf("added delete time [%s] to kvs [%v]", now.String(), kvIDs))
return nil
}
diff --git a/server/service/mongo/kv/kv_dao.go b/server/service/mongo/kv/kv_dao.go
index d612940..a776d0c 100644
--- a/server/service/mongo/kv/kv_dao.go
+++ b/server/service/mongo/kv/kv_dao.go
@@ -168,31 +168,67 @@ func findOneKey(ctx context.Context, filter bson.M) ([]*model.KVDoc, error) {
return []*model.KVDoc{curKV}, nil
}
-//deleteKV by kvID
-func deleteKV(ctx context.Context, kvID, project, domain string) error {
+//findOneKVAndDelete deletes one kv by id and return the deleted kv as these appeared before deletion
+func findOneKVAndDelete(ctx context.Context, kvID, project, domain string) (*model.KVDoc, error) {
+ collection := session.GetDB().Collection(session.CollectionKV)
+ sr := collection.FindOneAndDelete(ctx, bson.M{"id": kvID, "project": project, "domain": domain})
+ if sr.Err() != nil {
+ if sr.Err() == mongo.ErrNoDocuments {
+ return nil, service.ErrKeyNotExists
+ }
+ return nil, sr.Err()
+ }
+ openlogging.Info(fmt.Sprintf("delete success,kvID=%s", kvID))
if _, err := counter.ApplyRevision(ctx, domain); err != nil {
- openlogging.Error(fmt.Sprintf("increase revision failed, the kv [%s] not deleted: [%s]", kvID, err))
- return err
+ openlogging.Error(fmt.Sprintf("the kv [%s] is deleted, but increase revision failed: [%s]", kvID, err))
+ return nil, err
+ }
+ err := history.AddDeleteTime(ctx, []string{kvID}, project, domain)
+ if err != nil {
+ openlogging.Error(fmt.Sprintf("add delete time to [%s] failed : [%s]", kvID, err))
+ }
+ curKV := &model.KVDoc{}
+ err = sr.Decode(curKV)
+ if err != nil {
+ openlogging.Error("decode error: " + err.Error())
+ return nil, err
+ }
+ return curKV, nil
+}
+
+//findKVsAndDelete deletes multiple kvs and return the deleted kv list as these appeared before deletion
+func findKVsAndDelete(ctx context.Context, kvIDs []string, project, domain string) ([]*model.KVDoc, error) {
+ filter := bson.D{{"id", bson.M{"$in": kvIDs}}, {"project", project}, {"domain", domain}}
+ kvs, err := findKeys(ctx, filter, false)
+ if err != nil {
+ if err != service.ErrKeyNotExists {
+ openlogging.Error("find Keys error: " + err.Error())
+ }
+ return nil, err
}
collection := session.GetDB().Collection(session.CollectionKV)
- dr, err := collection.DeleteOne(ctx, bson.M{"id": kvID, "project": project, "domain": domain})
- //check error and delete number
+ dr, err := collection.DeleteMany(ctx, filter)
if err != nil {
- openlogging.Error(fmt.Sprintf("delete [%s] failed : [%s]", kvID, err))
- return err
+ openlogging.Error(fmt.Sprintf("delete kvs [%v] failed : [%v]", kvIDs, err))
+ return nil, err
}
- if dr.DeletedCount != 1 {
- openlogging.Warn(fmt.Sprintf("failed, may have been deleted,kvID=%s", kvID))
+ if int64(len(kvs)) != dr.DeletedCount {
+ openlogging.Warn(fmt.Sprintf("The count of found and the count of deleted are not equal, found %d, deleted %d", len(kvs), dr.DeletedCount))
} else {
- openlogging.Info(fmt.Sprintf("delete success,kvID=%s", kvID))
+ openlogging.Info(fmt.Sprintf("deleted %d kvs, their ids are %v", dr.DeletedCount, kvIDs))
+ }
+ if _, err := counter.ApplyRevision(ctx, domain); err != nil {
+ openlogging.Error(fmt.Sprintf("kvs [%v] are deleted, but increase revision failed: [%v]", kvIDs, err))
+ return nil, err
}
- err = history.AddDeleteTime(ctx, kvID, project, domain)
+ err = history.AddDeleteTime(ctx, kvIDs, project, domain)
if err != nil {
- openlogging.Error(fmt.Sprintf("add delete time to [%s] failed : [%s]", kvID, err))
+ openlogging.Error(fmt.Sprintf("add delete time to kvs [%s] failed : [%s]", kvIDs, err))
}
- return err
+ return kvs, nil
}
-func findKeys(ctx context.Context, filter bson.M, withoutLabel bool) ([]*model.KVDoc, error) {
+
+func findKeys(ctx context.Context, filter interface{}, withoutLabel bool) ([]*model.KVDoc, error) {
collection := session.GetDB().Collection(session.CollectionKV)
cur, err := collection.Find(ctx, filter)
if err != nil {
diff --git a/server/service/mongo/kv/kv_service.go b/server/service/mongo/kv/kv_service.go
index e5252ac..1a2526f 100644
--- a/server/service/mongo/kv/kv_service.go
+++ b/server/service/mongo/kv/kv_service.go
@@ -19,7 +19,6 @@ package kv
import (
"context"
- "errors"
"time"
"github.com/apache/servicecomb-kie/pkg/model"
@@ -46,6 +45,9 @@ type Service struct {
//Create will create a key value record
func (s *Service) Create(ctx context.Context, kv *model.KVDoc) (*model.KVDoc, error) {
ctx, _ = context.WithTimeout(ctx, session.Timeout)
+ if kv.Labels == nil {
+ kv.Labels = map[string]string{}
+ }
//check whether the project has certain labels or not
labelID, err := label.Exist(ctx, kv.Domain, kv.Project, kv.Labels)
if err != nil {
@@ -180,7 +182,9 @@ func (s *Service) Exist(ctx context.Context, domain, key string, project string,
if opts.LabelID != "" {
kvs, err := findKVByLabelID(ctx, domain, opts.LabelID, key, project)
if err != nil {
- openlogging.Error(err.Error())
+ if err != service.ErrKeyNotExists {
+ openlogging.Error(err.Error())
+ }
return nil, err
}
return kvs[0], nil
@@ -201,17 +205,17 @@ func (s *Service) Exist(ctx context.Context, domain, key string, project string,
}
-//Delete delete kv,If the labelID is "", query the collection kv to get it
+//FindOneAndDelete deletes one kv by id and return the deleted kv as these appeared before deletion
//domain=tenant
-func (s *Service) Delete(ctx context.Context, kvID string, domain string, project string) error {
+func (s *Service) FindOneAndDelete(ctx context.Context, kvID string, domain string, project string) (*model.KVDoc, error) {
ctx, _ = context.WithTimeout(context.Background(), session.Timeout)
- //delete kv
- err := deleteKV(ctx, kvID, project, domain)
- if err != nil {
- openlogging.Error("can not delete key, err:" + err.Error())
- return errors.New("can not delete key")
- }
- return nil
+ return findOneKVAndDelete(ctx, kvID, project, domain)
+}
+
+//FindManyAndDelete deletes multiple kvs and return the deleted kv list as these appeared before deletion
+func (s *Service) FindManyAndDelete(ctx context.Context, kvIDs []string, domain string, project string) ([]*model.KVDoc, error) {
+ ctx, _ = context.WithTimeout(context.Background(), session.Timeout)
+ return findKVsAndDelete(ctx, kvIDs, project, domain)
}
//List get kv list by key and criteria
diff --git a/server/service/mongo/kv/kv_test.go b/server/service/mongo/kv/kv_test.go
index 236d8ad..7283ce3 100644
--- a/server/service/mongo/kv/kv_test.go
+++ b/server/service/mongo/kv/kv_test.go
@@ -152,7 +152,7 @@ func TestService_Update(t *testing.T) {
func TestService_Delete(t *testing.T) {
kvsvc := &kv.Service{}
t.Run("delete kv by kvID", func(t *testing.T) {
- err := kvsvc.Delete(context.TODO(), id, "default", "kv-test")
+ _, err := kvsvc.FindOneAndDelete(context.TODO(), id, "default", "kv-test")
assert.NoError(t, err)
})
}
diff --git a/server/service/service.go b/server/service/service.go
index 00481af..e899468 100644
--- a/server/service/service.go
+++ b/server/service/service.go
@@ -47,7 +47,10 @@ type KV interface {
Update(ctx context.Context, kv *model.KVDoc) (*model.KVDoc, error)
CreateOrUpdate(ctx context.Context, kv *model.KVDoc) (*model.KVDoc, error)
List(ctx context.Context, domain, project string, options ...FindOption) (*model.KVResponse, error)
- Delete(ctx context.Context, kvID string, domain, project string) error
+ //FindOneAndDelete deletes one kv by id and return the deleted kv as these appeared before deletion
+ FindOneAndDelete(ctx context.Context, kvID string, domain, project string) (*model.KVDoc, error)
+ //FindManyAndDelete deletes multiple kvs and return the deleted kv list as these appeared before deletion
+ FindManyAndDelete(ctx context.Context, kvIDs []string, domain, project string) ([]*model.KVDoc, error)
//Get return kv by id
Get(ctx context.Context, domain, project, id string, options ...FindOption) (*model.KVDoc, error)
//KV is a resource of kie, this api should return kv resource number by domain id