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