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 80dc785  refactor apis (#129)
80dc785 is described below

commit 80dc785b4b1f303174480de772542d34cd1b700f
Author: zhulijian <[email protected]>
AuthorDate: Sat Mar 28 14:10:21 2020 +0800

    refactor apis (#129)
    
    * refactor apis of create and update kv
    
    Signed-off-by: zhulijian <[email protected]>
    
    * refactor apis of get and delete kv
    
    Signed-off-by: zhulijian <[email protected]>
    
    * ut
---
 docs/api.yaml                           | 144 ++++++++++-----------
 pkg/common/common.go                    |  32 ++---
 server/handler/track_handler.go         |   2 +-
 server/resource/v1/common.go            |  56 +++++++-
 server/resource/v1/doc_struct.go        |  29 +++--
 server/resource/v1/history_resource.go  |  16 +--
 server/resource/v1/kv_resource.go       | 222 +++++++++++++++++++++-----------
 server/resource/v1/kv_resource_test.go  | 111 ++++++++++++++--
 server/service/mongo/kv/kv_service.go   |  98 ++++++++++----
 server/service/mongo/kv/kv_test.go      |  58 ++++++---
 server/service/mongo/kv/tool.go         |  19 +--
 server/service/mongo/session/session.go |   2 +
 server/service/service.go               |   4 +-
 13 files changed, 525 insertions(+), 268 deletions(-)

diff --git a/docs/api.yaml b/docs/api.yaml
index 4171d65..c45264b 100644
--- a/docs/api.yaml
+++ b/docs/api.yaml
@@ -13,6 +13,9 @@ paths:
           in: path
           required: true
           type: string
+        - name: key
+          in: query
+          type: string
         - name: label
           in: query
           description: label pairs,for example 
&label=service:order&label=version:1.0.0
@@ -61,78 +64,49 @@ paths:
               type: integer
         "304":
           description: empty body
-        "404":
-          description: ""
-          headers:
-            X-Kie-Revision:
-              description: cluster latest revision number, if key value is 
changed,
-                it will increase.
-              type: integer
-    delete:
-      summary: delete key by kv ID.
-      operationId: Delete
+    post:
+      summary: create a key value
+      operationId: Post
       parameters:
         - name: project
           in: path
           required: true
           type: string
-        - name: kv_id
-          in: query
+        - name: Content-Type
+          in: header
+          description: used to indicate the media type of the resource, the 
value can
+            be application/json or text/yaml
           required: true
           type: string
+        - name: body
+          in: body
+          required: true
+          schema:
+            $ref: '#/definitions/v1.KVCreateBody'
       consumes:
-        - '*/*'
+        - application/json
+        - text/yaml
       produces:
-        - '*/*'
+        - application/json
+        - text/yaml
       responses:
-        "204":
-          description: Delete success
-        "500":
-          description: Server error
-  /v1/{project}/kie/kv/{key}:
+        "200":
+          description: ""
+          schema:
+            $ref: '#/definitions/DocResponseSingleKey'
+  /v1/{project}/kie/kv/{kv_id}:
     get:
-      summary: get key values by key and labels
-      operationId: GetByKey
+      summary: get key values by kv_id
+      operationId: Get
       parameters:
         - name: project
           in: path
           required: true
           type: string
-        - name: key
+        - name: kv_id
           in: path
           required: true
           type: string
-        - name: label
-          in: query
-          description: label pairs,for example 
&label=service:order&label=version:1.0.0
-          type: string
-        - name: wait
-          in: query
-          description: wait until any kv changed. for example wait=5s, server 
will not
-            response until 5 seconds, during that time window, if any kv 
changed, server
-            will return 200 and kv list, otherwise return 304 and empty body
-          type: string
-        - name: match
-          in: query
-          description: match works with label query param, it specifies label 
match
-            pattern. if it is empty, server will return kv which's labels 
partial match
-            the label query param. uf it is exact, server will return kv 
which's labels
-            exact match the label query param
-          type: string
-        - name: revision
-          in: query
-          description: each time you query,server will return a number in 
header X-Kie-Revision.
-            you can record it in client side, use this number as param value. 
if current
-            revision is greater than it, server will return data
-          type: string
-        - name: limit
-          in: query
-          description: pagination
-          type: string
-        - name: offset
-          in: query
-          description: pagination
-          type: string
       consumes:
         - '*/*'
       produces:
@@ -142,43 +116,37 @@ paths:
         "200":
           description: get key value success
           schema:
-            $ref: '#/definitions/DocResponseGetKey'
+            $ref: '#/definitions/DocResponseSingleKey'
           headers:
             X-Kie-Revision:
               description: cluster latest revision number, if key value is 
changed,
                 it will increase.
               type: integer
-        "304":
-          description: empty body
         "404":
-          description: ""
-          headers:
-            X-Kie-Revision:
-              description: cluster latest revision number, if key value is 
changed,
-                it will increase.
-              type: integer
+          description: key value not found
     put:
-      summary: create or update key value
+      summary: update a key value
       operationId: Put
       parameters:
         - name: project
           in: path
           required: true
           type: string
-        - name: key
+        - name: kv_id
           in: path
           required: true
           type: string
         - name: Content-Type
           in: header
+          description: used to indicate the media type of the resource, the 
value can
+            be application/json or text/yaml
           required: true
-          description: used to indicate the media type of the resource, the 
value can be application/json or text/yaml
           type: string
         - name: body
           in: body
           required: true
           schema:
-            $ref: '#/definitions/v1.KVBody'
+            $ref: '#/definitions/v1.KVUpdateBody'
       consumes:
         - application/json
         - text/yaml
@@ -190,7 +158,30 @@ paths:
           description: ""
           schema:
             $ref: '#/definitions/DocResponseSingleKey'
-  /v1/{project}/kie/revision/{key_id}:
+    delete:
+      summary: delete key by kv ID.
+      operationId: Delete
+      parameters:
+        - name: project
+          in: path
+          required: true
+          type: string
+        - name: kv_id
+          in: path
+          required: true
+          type: string
+      consumes:
+        - '*/*'
+      produces:
+        - '*/*'
+      responses:
+        "204":
+          description: delete success
+        "404":
+          description: no key value found for deletion
+        "500":
+          description: server error
+  /v1/{project}/kie/revision/{kv_id}:
     get:
       summary: get all revisions by key id
       operationId: GetRevisions
@@ -199,7 +190,7 @@ paths:
           in: path
           required: true
           type: string
-        - name: key_id
+        - name: kv_id
           in: path
           required: true
           type: string
@@ -213,9 +204,7 @@ paths:
         "200":
           description: ""
           schema:
-            type: array
-            items:
-              $ref: '#/definitions/DocResponseSingleKey'
+            $ref: '#/definitions/DocResponseGetKey'
   /v1/{project}/kie/track:
     get:
       summary: get polling tracks of clients of kie server
@@ -355,14 +344,25 @@ definitions:
         type: string
       user_agent:
         type: string
-  v1.KVBody:
+  v1.KVCreateBody:
     type: object
     properties:
+      key:
+        type: string
       labels:
         type: object
         additionalProperties:
           type: string
+      status:
+        type: string
       value:
         type: string
       value_type:
         type: string
+  v1.KVUpdateBody:
+    type: object
+    properties:
+      status:
+        type: string
+      value:
+        type: string
diff --git a/pkg/common/common.go b/pkg/common/common.go
index 82449c3..097a2b1 100644
--- a/pkg/common/common.go
+++ b/pkg/common/common.go
@@ -21,16 +21,18 @@ import "time"
 
 //match mode
 const (
-       QueryParamQ      = "q"
-       QueryByLabelsCon = "&"
-       QueryParamWait   = "wait"
-       QueryParamRev    = "revision"
-       QueryParamMatch  = "match"
-       QueryParamKeyID  = "kv_id"
-       QueryParamLabel  = "label"
-       QueryParamStatus = "status"
-       QueryParamOffset = "offset"
-       QueryParamLimit  = "limit"
+       QueryParamQ          = "q"
+       QueryByLabelsCon     = "&"
+       QueryParamWait       = "wait"
+       QueryParamRev        = "revision"
+       QueryParamMatch      = "match"
+       QueryParamKey        = "key"
+       QueryParamLabel      = "label"
+       QueryParamStatus     = "status"
+       QueryParamOffset     = "offset"
+       QueryParamLimit      = "limit"
+       PathParamKVID        = "kv_id"
+       PathParameterProject = "project"
        //polling data
        QueryParamSessionID = "sessionId"
        QueryParamIP        = "ip"
@@ -59,13 +61,13 @@ const (
        StatusEnabled           = "enabled"
        StatusDisabled          = "disabled"
        MsgDomainMustNotBeEmpty = "domain must not be empty"
+       MsgDeleteKVFailed       = "delete kv failed"
        MsgIllegalLabels        = "label value can not be empty, " +
                "label can not be duplicated, please check query parameters"
-       MsgIllegalDepth     = "X-Depth must be number"
-       MsgInvalidWait      = "wait param should be formed with number and time 
unit like 5s,100ms, and less than 5m"
-       MsgInvalidRev       = "revision param should be formed with number 
greater than 0"
-       ErrKvIDMustNotEmpty = "must supply kv id if you want to remove key"
-       RespBodyContextKey  = "responseBody"
+       MsgIllegalDepth    = "X-Depth must be number"
+       MsgInvalidWait     = "wait param should be formed with number and time 
unit like 5s,100ms, and less than 5m"
+       MsgInvalidRev      = "revision param should be formed with number 
greater than 0"
+       RespBodyContextKey = "responseBody"
 
        MaxWait = 5 * time.Minute
 )
diff --git a/server/handler/track_handler.go b/server/handler/track_handler.go
index 1fc1122..88028d7 100644
--- a/server/handler/track_handler.go
+++ b/server/handler/track_handler.go
@@ -77,7 +77,7 @@ func (h *TrackHandler) Handle(chain *handler.Chain, inv 
*invocation.Invocation,
                data.PollingData = map[string]interface{}{
                        "revStr":  revStr,
                        "wait":    wait,
-                       "project": req.HeaderParameter(v1.PathParameterProject),
+                       "project": 
req.HeaderParameter(common.PathParameterProject),
                        "labels":  req.QueryParameter("label"),
                }
                _, err := track.CreateOrUpdate(inv.Ctx, data)
diff --git a/server/resource/v1/common.go b/server/resource/v1/common.go
index d84ea45..3e1a7ed 100644
--- a/server/resource/v1/common.go
+++ b/server/resource/v1/common.go
@@ -22,6 +22,7 @@ import (
        "encoding/json"
        "errors"
        "github.com/apache/servicecomb-kie/pkg/model"
+       "github.com/apache/servicecomb-kie/server/service/mongo/session"
        "net/http"
        "strconv"
        "strings"
@@ -39,12 +40,9 @@ import (
 
 //const of server
 const (
-       HeaderUserAgent      = "User-Agent"
-       HeaderSessionID      = "X-Session-Id"
-       PathParameterProject = "project"
-       PathParameterKey     = "key"
-       AttributeDomainKey   = "domain"
-       MsgLabelsNotFound    = "can not find by labels"
+       HeaderUserAgent    = "User-Agent"
+       HeaderSessionID    = "X-Session-Id"
+       AttributeDomainKey = "domain"
 )
 
 //err
@@ -212,6 +210,52 @@ func checkPagination(offsetStr, limitStr string) (int64, 
int64, error) {
        return offset, limit, err
 }
 
+func validatePost(kv *model.KVDoc) error {
+       err := checkDomainAndProject(kv.Domain, kv.Project)
+       if err != nil {
+               return err
+       }
+       if kv.Key == "" {
+               return session.ErrKeyIsNil
+       }
+       _, err = checkStatus(kv.Status)
+       return err
+}
+
+func validatePut(kv *model.KVDoc) error {
+       err := validateGet(kv.Domain, kv.Project, kv.ID)
+       if err != nil {
+               return err
+       }
+       _, err = checkStatus(kv.Status)
+       return err
+}
+
+func validateGet(domain, project, kvID string) error {
+       if kvID == "" {
+               return session.ErrIDIsNil
+       }
+       return checkDomainAndProject(domain, project)
+}
+
+func validateList(domain, project string) error {
+       return checkDomainAndProject(domain, project)
+}
+
+func validateDelete(domain, project, kvID string) error {
+       return validateGet(domain, project, kvID)
+}
+
+func checkDomainAndProject(domain, project string) error {
+       if domain == "" {
+               return session.ErrMissingDomain
+       }
+       if project == "" {
+               return session.ErrMissingProject
+       }
+       return nil
+}
+
 func checkStatus(status string) (string, error) {
        if status != "" {
                if status != common.StatusEnabled && status != 
common.StatusDisabled {
diff --git a/server/resource/v1/doc_struct.go b/server/resource/v1/doc_struct.go
index a56ec08..63f2613 100644
--- a/server/resource/v1/doc_struct.go
+++ b/server/resource/v1/doc_struct.go
@@ -88,11 +88,10 @@ var (
                        "if it is empty, server will return kv which's labels 
partial match the label query param. " +
                        "uf it is exact, server will return kv which's labels 
exact match the label query param",
        }
-       DocQueryKeyIDParameters = &restful.Parameters{
+       DocQueryKeyParameters = &restful.Parameters{
                DataType:  "string",
-               Name:      common.QueryParamKeyID,
+               Name:      common.QueryParamKey,
                ParamType: goRestful.QueryParameterKind,
-               Required:  true,
        }
        DocQueryLabelParameters = &restful.Parameters{
                DataType:  "string",
@@ -141,31 +140,33 @@ var (
 
 //swagger doc path params
 var (
-       DocPathKey = &restful.Parameters{
-               DataType:  "string",
-               Name:      "key",
-               ParamType: goRestful.PathParameterKind,
-               Required:  true,
-       }
        DocPathProject = &restful.Parameters{
                DataType:  "string",
-               Name:      "project",
+               Name:      common.PathParameterProject,
                ParamType: goRestful.PathParameterKind,
                Required:  true,
        }
        DocPathKeyID = &restful.Parameters{
                DataType:  "string",
-               Name:      "key_id",
+               Name:      common.PathParamKVID,
                ParamType: goRestful.PathParameterKind,
                Required:  true,
        }
 )
 
-//KVBody is open api doc
-type KVBody struct {
+//KVCreateBody is open api doc
+type KVCreateBody struct {
+       Key       string            `json:"key"`
        Labels    map[string]string `json:"labels"`
-       ValueType string            `json:"value_type"`
+       Status    string            `json:"status"`
        Value     string            `json:"value"`
+       ValueType string            `json:"value_type"`
+}
+
+//KVUpdateBody is open api doc
+type KVUpdateBody struct {
+       Status string `json:"status"`
+       Value  string `json:"value"`
 }
 
 //ErrorMsg is open api doc
diff --git a/server/resource/v1/history_resource.go 
b/server/resource/v1/history_resource.go
index 0e0ff97..5c27ee0 100644
--- a/server/resource/v1/history_resource.go
+++ b/server/resource/v1/history_resource.go
@@ -40,7 +40,7 @@ type HistoryResource struct {
 //GetRevisions search key only by label
 func (r *HistoryResource) GetRevisions(context *restful.Context) {
        var err error
-       keyID := context.ReadPathParameter("key_id")
+       kvID := context.ReadPathParameter(common.PathParamKVID)
        offsetStr := context.ReadQueryParameter(common.QueryParamOffset)
        limitStr := context.ReadQueryParameter(common.QueryParamLimit)
        offset, limit, err := checkPagination(offsetStr, limitStr)
@@ -48,14 +48,12 @@ func (r *HistoryResource) GetRevisions(context 
*restful.Context) {
                WriteErrResponse(context, http.StatusBadRequest, err.Error(), 
common.ContentTypeText)
                return
        }
-       if keyID == "" {
-               openlogging.Error("key id is nil")
-               WriteErrResponse(context, http.StatusForbidden, "key_id must 
not be empty", common.ContentTypeText)
+       if kvID == "" {
+               openlogging.Error("kv id is nil")
+               WriteErrResponse(context, http.StatusForbidden, "kv_id must not 
be empty", common.ContentTypeText)
                return
        }
-       key := context.ReadQueryParameter("key")
-       revisions, err := service.HistoryService.GetHistory(context.Ctx, keyID,
-               service.WithKey(key),
+       revisions, err := service.HistoryService.GetHistory(context.Ctx, kvID,
                service.WithOffset(offset),
                service.WithLimit(limit))
        if err != nil {
@@ -138,7 +136,7 @@ func (r *HistoryResource) URLPatterns() []restful.Route {
        return []restful.Route{
                {
                        Method:       http.MethodGet,
-                       Path:         "/v1/{project}/kie/revision/{key_id}",
+                       Path:         "/v1/{project}/kie/revision/{kv_id}",
                        ResourceFunc: r.GetRevisions,
                        FuncDesc:     "get all revisions by key id",
                        Parameters: []*restful.Parameters{
@@ -147,7 +145,7 @@ func (r *HistoryResource) URLPatterns() []restful.Route {
                        Returns: []*restful.Returns{
                                {
                                        Code:  http.StatusOK,
-                                       Model: []model.DocResponseSingleKey{},
+                                       Model: model.DocResponseGetKey{},
                                },
                        },
                        Consumes: []string{goRestful.MIME_JSON, 
common.ContentTypeYaml},
diff --git a/server/resource/v1/kv_resource.go 
b/server/resource/v1/kv_resource.go
index 86ef153..df93981 100644
--- a/server/resource/v1/kv_resource.go
+++ b/server/resource/v1/kv_resource.go
@@ -19,6 +19,7 @@
 package v1
 
 import (
+       "context"
        "fmt"
        "net/http"
 
@@ -26,6 +27,7 @@ import (
        "github.com/apache/servicecomb-kie/pkg/model"
        "github.com/apache/servicecomb-kie/server/pubsub"
        "github.com/apache/servicecomb-kie/server/service"
+       "github.com/apache/servicecomb-kie/server/service/mongo/session"
        goRestful "github.com/emicklei/go-restful"
        "github.com/go-chassis/go-chassis/server/restful"
        "github.com/go-mesh/openlogging"
@@ -35,29 +37,31 @@ import (
 type KVResource struct {
 }
 
-//Put create or update kv
-func (r *KVResource) Put(context *restful.Context) {
+//Post create a kv
+func (r *KVResource) Post(rctx *restful.Context) {
        var err error
-       key := context.ReadPathParameter(PathParameterKey)
-       project := context.ReadPathParameter(PathParameterProject)
+       project := rctx.ReadPathParameter(common.PathParameterProject)
        kv := new(model.KVDoc)
-       if err = readRequest(context, kv); err != nil {
-               WriteErrResponse(context, http.StatusBadRequest, err.Error(), 
common.ContentTypeText)
+       if err = readRequest(rctx, kv); err != nil {
+               WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), 
common.ContentTypeText)
                return
        }
-       domain := ReadDomain(context)
-       kv.Key = key
+       domain := ReadDomain(rctx)
        kv.Domain = domain.(string)
        kv.Project = project
-       _, err = checkStatus(kv.Status)
+       err = validatePost(kv)
        if err != nil {
-               WriteErrResponse(context, http.StatusInternalServerError, 
err.Error(), common.ContentTypeText)
+               WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), 
common.ContentTypeText)
                return
        }
-       kv, err = service.KVService.CreateOrUpdate(context.Ctx, kv)
+       kv, err = service.KVService.Create(rctx.Ctx, kv)
        if err != nil {
-               openlogging.Error(fmt.Sprintf("put [%v] err:%s", kv, 
err.Error()))
-               WriteErrResponse(context, http.StatusInternalServerError, 
err.Error(), common.ContentTypeText)
+               openlogging.Error(fmt.Sprintf("post err:%s", err.Error()))
+               if err == session.ErrKVAlreadyExists {
+                       WriteErrResponse(rctx, http.StatusConflict, 
err.Error(), common.ContentTypeText)
+                       return
+               }
+               WriteErrResponse(rctx, http.StatusInternalServerError, "create 
kv failed", common.ContentTypeText)
                return
        }
        err = pubsub.Publish(&pubsub.KVChangeEvent{
@@ -68,65 +72,106 @@ func (r *KVResource) Put(context *restful.Context) {
                Action:   pubsub.ActionPut,
        })
        if err != nil {
-               openlogging.Warn("lost kv change event:" + err.Error())
+               openlogging.Warn("lost kv change event when post:" + 
err.Error())
        }
        openlogging.Info(
-               fmt.Sprintf("put [%s] success", kv.Key))
-       err = writeResponse(context, kv)
+               fmt.Sprintf("post [%s] success", kv.ID))
+       err = writeResponse(rctx, kv)
        if err != nil {
                openlogging.Error(err.Error())
        }
 
 }
 
-//GetByKey search key by label and key
-func (r *KVResource) GetByKey(rctx *restful.Context) {
+//Put update a kv
+func (r *KVResource) Put(rctx *restful.Context) {
        var err error
-       key := rctx.ReadPathParameter(PathParameterKey)
-       if key == "" {
-               WriteErrResponse(rctx, http.StatusBadRequest, "key must not be 
empty", common.ContentTypeText)
+       kvID := rctx.ReadPathParameter(common.PathParamKVID)
+       project := rctx.ReadPathParameter(common.PathParameterProject)
+       kv := new(model.KVDoc)
+       if err = readRequest(rctx, kv); err != nil {
+               WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), 
common.ContentTypeText)
                return
        }
-       project := rctx.ReadPathParameter(PathParameterProject)
-       labels, err := getLabels(rctx)
+       domain := ReadDomain(rctx)
+       kv.ID = kvID
+       kv.Domain = domain.(string)
+       kv.Project = project
+       err = validatePut(kv)
        if err != nil {
-               WriteErrResponse(rctx, http.StatusBadRequest, 
common.MsgIllegalLabels, common.ContentTypeText)
+               WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), 
common.ContentTypeText)
                return
        }
-       domain := ReadDomain(rctx)
-       offsetStr := rctx.ReadQueryParameter(common.QueryParamOffset)
-       limitStr := rctx.ReadQueryParameter(common.QueryParamLimit)
-       offset, limit, err := checkPagination(offsetStr, limitStr)
+       kv, err = service.KVService.Update(rctx.Ctx, kv)
        if err != nil {
-               WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), 
common.ContentTypeText)
+               openlogging.Error(fmt.Sprintf("put [%s] err:%s", kvID, 
err.Error()))
+               WriteErrResponse(rctx, http.StatusInternalServerError, "update 
kv failed", common.ContentTypeText)
                return
        }
-       sessionID := rctx.ReadHeader(HeaderSessionID)
-       statusStr := rctx.ReadQueryParameter(common.QueryParamStatus)
-       status, err := checkStatus(statusStr)
+       err = pubsub.Publish(&pubsub.KVChangeEvent{
+               Key:      kv.Key,
+               Labels:   kv.Labels,
+               Project:  project,
+               DomainID: kv.Domain,
+               Action:   pubsub.ActionPut,
+       })
+       if err != nil {
+               openlogging.Warn("lost kv change event when put:" + err.Error())
+       }
+       openlogging.Info(
+               fmt.Sprintf("put [%s] success", kvID))
+       err = writeResponse(rctx, kv)
+       if err != nil {
+               openlogging.Error(err.Error())
+       }
+
+}
+
+//Get search key by kv id
+func (r *KVResource) Get(rctx *restful.Context) {
+       project := rctx.ReadPathParameter(common.PathParameterProject)
+       domain := ReadDomain(rctx).(string)
+       kvID := rctx.ReadPathParameter(common.PathParamKVID)
+       err := validateGet(domain, project, kvID)
        if err != nil {
                WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), 
common.ContentTypeText)
                return
        }
-       returnData(rctx, &model.KVDoc{
-               Domain:  domain.(string),
-               Project: project,
-               Key:     key,
-               Labels:  labels,
-               Status:  status,
-       }, offset, limit, sessionID)
+       kv, err := service.KVService.Get(rctx.Ctx, domain, project, kvID)
+       if err != nil {
+               openlogging.Error("kv_resource: " + err.Error())
+               if err == service.ErrKeyNotExists {
+                       WriteErrResponse(rctx, http.StatusNotFound, 
err.Error(), common.ContentTypeText)
+                       return
+               }
+               WriteErrResponse(rctx, http.StatusInternalServerError, "get kv 
failed", common.ContentTypeText)
+               return
+       }
+       kv.Domain = ""
+       kv.Project = ""
+       err = writeResponse(rctx, kv)
+       rctx.Ctx = context.WithValue(rctx.Ctx, common.RespBodyContextKey, kv)
+       if err != nil {
+               openlogging.Error(err.Error())
+       }
 }
 
 //List response kv list
 func (r *KVResource) List(rctx *restful.Context) {
        var err error
-       project := rctx.ReadPathParameter(PathParameterProject)
-       domain := ReadDomain(rctx)
-       labels, err := getLabels(rctx)
+       key := rctx.ReadQueryParameter(common.QueryParamKey)
+       project := rctx.ReadPathParameter(common.PathParameterProject)
+       domain := ReadDomain(rctx).(string)
+       err = validateList(domain, project)
        if err != nil {
                WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), 
common.ContentTypeText)
                return
        }
+       labels, err := getLabels(rctx)
+       if err != nil {
+               WriteErrResponse(rctx, http.StatusBadRequest, 
common.MsgIllegalLabels, common.ContentTypeText)
+               return
+       }
 
        offsetStr := rctx.ReadQueryParameter(common.QueryParamOffset)
        limitStr := rctx.ReadQueryParameter(common.QueryParamLimit)
@@ -143,8 +188,9 @@ func (r *KVResource) List(rctx *restful.Context) {
                return
        }
        returnData(rctx, &model.KVDoc{
-               Domain:  domain.(string),
+               Domain:  domain,
                Project: project,
+               Key:     key,
                Labels:  labels,
                Status:  status,
        }, offset, limit, sessionID)
@@ -210,57 +256,76 @@ func returnData(rctx *restful.Context, doc *model.KVDoc, 
offset, limit int64, se
 }
 
 //Delete deletes key by ids
-func (r *KVResource) Delete(context *restful.Context) {
-       project := context.ReadPathParameter(PathParameterProject)
-       domain := ReadDomain(context)
-       kvID := context.ReadQueryParameter(common.QueryParamKeyID)
-       if kvID == "" {
-               WriteErrResponse(context, http.StatusBadRequest, 
common.ErrKvIDMustNotEmpty, common.ContentTypeText)
+func (r *KVResource) Delete(rctx *restful.Context) {
+       project := rctx.ReadPathParameter(common.PathParameterProject)
+       domain := ReadDomain(rctx).(string)
+       kvID := rctx.ReadPathParameter(common.PathParamKVID)
+       err := validateDelete(domain, project, kvID)
+       if err != nil {
+               WriteErrResponse(rctx, http.StatusBadRequest, err.Error(), 
common.ContentTypeText)
                return
        }
-       result, err := service.KVService.Get(context.Ctx, domain.(string), 
project, kvID)
-       if err != nil && err != service.ErrKeyNotExists {
-               WriteErrResponse(context, http.StatusInternalServerError, 
err.Error(), common.ContentTypeText)
-               return
-       } else if err == service.ErrKeyNotExists {
-               context.WriteHeader(http.StatusNoContent)
+       kv, err := service.KVService.Get(rctx.Ctx, domain, project, kvID)
+       if err != nil {
+               openlogging.Error("kv_resource: " + err.Error())
+               if err == service.ErrKeyNotExists {
+                       WriteErrResponse(rctx, http.StatusNotFound, 
err.Error(), common.ContentTypeText)
+                       return
+               }
+               WriteErrResponse(rctx, http.StatusInternalServerError, 
common.MsgDeleteKVFailed, common.ContentTypeText)
                return
        }
-       kv := result.Data[0]
-       err = service.KVService.Delete(context.Ctx, kvID, domain.(string), 
project)
+       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(context, http.StatusInternalServerError, 
err.Error(), common.ContentTypeText)
+               WriteErrResponse(rctx, http.StatusInternalServerError, 
common.MsgDeleteKVFailed, common.ContentTypeText)
                return
        }
        err = pubsub.Publish(&pubsub.KVChangeEvent{
                Key:      kv.Key,
                Labels:   kv.Labels,
                Project:  project,
-               DomainID: domain.(string),
+               DomainID: domain,
                Action:   pubsub.ActionDelete,
        })
        if err != nil {
                openlogging.Warn("lost kv change event:" + err.Error())
        }
-       context.WriteHeader(http.StatusNoContent)
+       rctx.WriteHeader(http.StatusNoContent)
 }
 
 //URLPatterns defined config operations
 func (r *KVResource) URLPatterns() []restful.Route {
        return []restful.Route{
                {
+                       Method:       http.MethodPost,
+                       Path:         "/v1/{project}/kie/kv",
+                       ResourceFunc: r.Post,
+                       FuncDesc:     "create a key value",
+                       Parameters: []*restful.Parameters{
+                               DocPathProject, DocHeaderContentType,
+                       },
+                       Read: KVCreateBody{},
+                       Returns: []*restful.Returns{
+                               {
+                                       Code:  http.StatusOK,
+                                       Model: model.DocResponseSingleKey{},
+                               },
+                       },
+                       Consumes: []string{goRestful.MIME_JSON, 
common.ContentTypeYaml},
+                       Produces: []string{goRestful.MIME_JSON, 
common.ContentTypeYaml},
+               }, {
                        Method:       http.MethodPut,
-                       Path:         "/v1/{project}/kie/kv/{key}",
+                       Path:         "/v1/{project}/kie/kv/{kv_id}",
                        ResourceFunc: r.Put,
-                       FuncDesc:     "create or update key value",
+                       FuncDesc:     "update a key value",
                        Parameters: []*restful.Parameters{
-                               DocPathProject, DocPathKey, 
DocHeaderContentType,
+                               DocPathProject, DocPathKeyID, 
DocHeaderContentType,
                        },
-                       Read: KVBody{},
+                       Read: KVUpdateBody{},
                        Returns: []*restful.Returns{
                                {
                                        Code:  http.StatusOK,
@@ -271,25 +336,24 @@ func (r *KVResource) URLPatterns() []restful.Route {
                        Produces: []string{goRestful.MIME_JSON, 
common.ContentTypeYaml},
                }, {
                        Method:       http.MethodGet,
-                       Path:         "/v1/{project}/kie/kv/{key}",
-                       ResourceFunc: r.GetByKey,
-                       FuncDesc:     "get key values by key and labels",
+                       Path:         "/v1/{project}/kie/kv/{kv_id}",
+                       ResourceFunc: r.Get,
+                       FuncDesc:     "get key values by kv_id",
                        Parameters: []*restful.Parameters{
-                               DocPathProject, DocPathKey, 
DocQueryLabelParameters, DocQueryWait, DocQueryMatch, DocQueryRev,
-                               DocQueryLimitParameters, 
DocQueryOffsetParameters,
+                               DocPathProject, DocPathKeyID,
                        },
                        Returns: []*restful.Returns{
                                {
                                        Code:    http.StatusOK,
                                        Message: "get key value success",
-                                       Model:   model.DocResponseGetKey{},
+                                       Model:   model.DocResponseSingleKey{},
                                        Headers: map[string]goRestful.Header{
                                                common.HeaderRevision: 
DocHeaderRevision,
                                        },
                                },
                                {
-                                       Code:    http.StatusNotModified,
-                                       Message: "empty body",
+                                       Code:    http.StatusNotFound,
+                                       Message: "key value not found",
                                },
                        },
                        Produces: []string{goRestful.MIME_JSON, 
common.ContentTypeYaml},
@@ -299,7 +363,7 @@ func (r *KVResource) URLPatterns() []restful.Route {
                        ResourceFunc: r.List,
                        FuncDesc:     "list key values by labels and key",
                        Parameters: []*restful.Parameters{
-                               DocPathProject, DocQueryLabelParameters, 
DocQueryWait, DocQueryMatch, DocQueryRev,
+                               DocPathProject, DocQueryKeyParameters, 
DocQueryLabelParameters, DocQueryWait, DocQueryMatch, DocQueryRev,
                                DocQueryLimitParameters, 
DocQueryOffsetParameters,
                        },
                        Returns: []*restful.Returns{
@@ -317,21 +381,25 @@ func (r *KVResource) URLPatterns() []restful.Route {
                        Produces: []string{goRestful.MIME_JSON, 
common.ContentTypeYaml},
                }, {
                        Method:       http.MethodDelete,
-                       Path:         "/v1/{project}/kie/kv",
+                       Path:         "/v1/{project}/kie/kv/{kv_id}",
                        ResourceFunc: r.Delete,
                        FuncDesc:     "delete key by kv ID.",
                        Parameters: []*restful.Parameters{
                                DocPathProject,
-                               DocQueryKeyIDParameters,
+                               DocPathKeyID,
                        },
                        Returns: []*restful.Returns{
                                {
                                        Code:    http.StatusNoContent,
-                                       Message: "Delete success",
+                                       Message: "delete success",
+                               },
+                               {
+                                       Code:    http.StatusNotFound,
+                                       Message: "no key value found for 
deletion",
                                },
                                {
                                        Code:    http.StatusInternalServerError,
-                                       Message: "Server error",
+                                       Message: "server error",
                                },
                        },
                },
diff --git a/server/resource/v1/kv_resource_test.go 
b/server/resource/v1/kv_resource_test.go
index fcd789e..6b8f27e 100644
--- a/server/resource/v1/kv_resource_test.go
+++ b/server/resource/v1/kv_resource_test.go
@@ -65,14 +65,15 @@ func init() {
        pubsub.Init()
        pubsub.Start()
 }
-func TestKVResource_Put(t *testing.T) {
-       t.Run("put kv, label is service", func(t *testing.T) {
+func TestKVResource_Post(t *testing.T) {
+       t.Run("post kv, label is service", func(t *testing.T) {
                kv := &model.KVDoc{
+                       Key:    "timeout",
                        Value:  "1s",
                        Labels: map[string]string{"service": "utService"},
                }
                j, _ := json.Marshal(kv)
-               r, _ := http.NewRequest("PUT", "/v1/test/kie/kv/timeout", 
bytes.NewBuffer(j))
+               r, _ := http.NewRequest("POST", "/v1/test/kie/kv", 
bytes.NewBuffer(j))
                noopH := &handler2.NoopAuthHandler{}
                chain, _ := handler.CreateChain(common.Provider, "testchain1", 
noopH.Name())
                r.Header.Set("Content-Type", "application/json")
@@ -90,13 +91,14 @@ func TestKVResource_Put(t *testing.T) {
                assert.Equal(t, kv.Value, data.Value)
                assert.Equal(t, kv.Labels, data.Labels)
        })
-       t.Run("put a different key, which label is same to timeout", func(t 
*testing.T) {
+       t.Run("post a different key, which label is same to timeout", func(t 
*testing.T) {
                kv := &model.KVDoc{
+                       Key:    "interval",
                        Value:  "1s",
                        Labels: map[string]string{"service": "utService"},
                }
                j, _ := json.Marshal(kv)
-               r, _ := http.NewRequest("PUT", "/v1/test/kie/kv/interval", 
bytes.NewBuffer(j))
+               r, _ := http.NewRequest("POST", "/v1/test/kie/kv", 
bytes.NewBuffer(j))
                noopH := &handler2.NoopAuthHandler{}
                chain, _ := handler.CreateChain(common.Provider, "testchain1", 
noopH.Name())
                r.Header.Set("Content-Type", "application/json")
@@ -114,15 +116,16 @@ func TestKVResource_Put(t *testing.T) {
                assert.Equal(t, kv.Value, data.Value)
                assert.Equal(t, kv.Labels, data.Labels)
        })
-       t.Run("put kv,label is service and version", func(t *testing.T) {
+       t.Run("post kv,label is service and version", func(t *testing.T) {
                kv := &model.KVDoc{
+                       Key:   "timeout",
                        Value: "1s",
                        Labels: map[string]string{
                                "service": "utService",
                                "version": "1.0.0"},
                }
                j, _ := json.Marshal(kv)
-               r, _ := http.NewRequest("PUT", "/v1/test/kie/kv/timeout", 
bytes.NewBuffer(j))
+               r, _ := http.NewRequest("POST", "/v1/test/kie/kv", 
bytes.NewBuffer(j))
                noopH := &handler2.NoopAuthHandler{}
                chain, _ := handler.CreateChain(common.Provider, "testchain1", 
noopH.Name())
                r.Header.Set("Content-Type", "application/json")
@@ -277,11 +280,12 @@ func TestKVResource_List(t *testing.T) {
                wg.Add(1)
                go func() {
                        kv := &model.KVDoc{
+                               Key:    "testKey",
                                Value:  "val",
                                Labels: map[string]string{"dummy": "test", 
"match": "test"},
                        }
                        j, _ := json.Marshal(kv)
-                       r2, _ := http.NewRequest("PUT", 
"/v1/test/kie/kv/testKey", bytes.NewBuffer(j))
+                       r2, _ := http.NewRequest("POST", "/v1/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")
@@ -305,10 +309,8 @@ func TestKVResource_List(t *testing.T) {
                assert.Equal(t, 304, resp.Code)
                t.Log(duration)
        })
-}
-func TestKVResource_GetByKey(t *testing.T) {
        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/timeout?label=service:utService&match=exact", nil)
+               r, _ := http.NewRequest("GET", 
"/v1/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")
@@ -325,7 +327,7 @@ func TestKVResource_GetByKey(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/timeout?label=service:utService", nil)
+               r, _ := http.NewRequest("GET", 
"/v1/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")
@@ -341,7 +343,7 @@ func TestKVResource_GetByKey(t *testing.T) {
                assert.NoError(t, err)
                assert.Equal(t, 2, len(result.Data))
 
-               r2, _ := http.NewRequest("DELETE", 
"/v1/test/kie/kv?kv_id="+result.Data[0].ID, nil)
+               r2, _ := http.NewRequest("DELETE", 
"/v1/test/kie/kv/"+result.Data[0].ID, nil)
                c2, err := restfultest.New(kvr, chain)
                assert.NoError(t, err)
                resp2 := httptest.NewRecorder()
@@ -350,3 +352,86 @@ func TestKVResource_GetByKey(t *testing.T) {
 
        })
 }
+func TestKVResource_PutAndGet(t *testing.T) {
+       var id string
+       kv := &model.KVDoc{
+               Key:    "user",
+               Value:  "guest",
+               Labels: map[string]string{"service": "utService"},
+       }
+       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))
+               noopH := &handler2.NoopAuthHandler{}
+               chain, _ := handler.CreateChain(common.Provider, "testchain1", 
noopH.Name())
+               r.Header.Set("Content-Type", "application/json")
+               kvr := &v1.KVResource{}
+               c, _ := restfultest.New(kvr, chain)
+               resp := httptest.NewRecorder()
+               c.ServeHTTP(resp, r)
+
+               body, err := ioutil.ReadAll(resp.Body)
+               assert.NoError(t, err)
+               result := &model.KVDoc{}
+               err = json.Unmarshal(body, result)
+               assert.NoError(t, err)
+               assert.NotEmpty(t, result.ID)
+               assert.Equal(t, kv.Value, result.Value)
+               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)
+               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.KVDoc{}
+               err = json.Unmarshal(body, result)
+               assert.NoError(t, err)
+               assert.Equal(t, kv.Value, result.Value)
+       })
+       kvUpdate := &model.KVDoc{
+               Value: "admin",
+       }
+       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))
+               noopH := &handler2.NoopAuthHandler{}
+               chain, _ := handler.CreateChain(common.Provider, "testchain1", 
noopH.Name())
+               r.Header.Set("Content-Type", "application/json")
+               kvr := &v1.KVResource{}
+               c, _ := restfultest.New(kvr, chain)
+               resp := httptest.NewRecorder()
+               c.ServeHTTP(resp, r)
+
+               body, err := ioutil.ReadAll(resp.Body)
+               assert.NoError(t, err)
+               result := &model.KVDoc{}
+               err = json.Unmarshal(body, result)
+               assert.NoError(t, err)
+               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)
+               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.KVDoc{}
+               err = json.Unmarshal(body, result)
+               assert.NoError(t, err)
+               assert.Equal(t, kvUpdate.Value, result.Value)
+       })
+}
diff --git a/server/service/mongo/kv/kv_service.go 
b/server/service/mongo/kv/kv_service.go
index 5b01e0b..7bf2011 100644
--- a/server/service/mongo/kv/kv_service.go
+++ b/server/service/mongo/kv/kv_service.go
@@ -32,12 +32,10 @@ import (
 
 //const of kv service
 const (
-       MsgFindKvFailed    = "find kv failed, deadline exceeded"
-       MsgFindOneKey      = "find one key"
-       MsgFindOneKeyByID  = "find one key by id"
-       MsgFindMoreKey     = "find more"
-       MsgHitExactLabels  = "hit exact labels"
-       FmtErrFindKvFailed = "can not find kv in %s"
+       MsgFindKvFailed      = "find kv failed, deadline exceeded"
+       MsgFindOneKey        = "find one key"
+       MsgCreateLabelFailed = "create label failed"
+       FmtErrFindKvFailed   = "can not find kv in %s"
 )
 
 //Service operate data in mongodb
@@ -45,6 +43,70 @@ type Service struct {
        timeout time.Duration
 }
 
+//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)
+       //check whether the project has certain labels or not
+       labelID, err := label.Exist(ctx, kv.Domain, kv.Project, kv.Labels)
+       if err != nil {
+               if err == session.ErrLabelNotExists {
+                       l := &model.LabelDoc{
+                               Domain:  kv.Domain,
+                               Labels:  kv.Labels,
+                               Project: kv.Project,
+                       }
+                       l, err = label.CreateLabel(ctx, l)
+                       if err != nil {
+                               openlogging.Error(MsgCreateLabelFailed, 
openlogging.WithTags(openlogging.Tags{
+                                       "k":      kv.Key,
+                                       "domain": kv.Domain,
+                               }))
+                               return nil, err
+                       }
+                       labelID = l.ID
+               } else {
+                       return nil, err
+               }
+       }
+       kv.LabelID = labelID
+       if kv.ValueType == "" {
+               kv.ValueType = session.DefaultValueType
+       }
+       _, err = s.Exist(ctx, kv.Domain, kv.Key, kv.Project, 
service.WithLabelID(kv.LabelID))
+       if err == nil {
+               return nil, session.ErrKVAlreadyExists
+       }
+       if err != service.ErrKeyNotExists {
+               openlogging.Error(err.Error())
+               return nil, err
+       }
+       kv, err = createKey(ctx, kv)
+       if err != nil {
+               openlogging.Error(err.Error())
+               return nil, err
+       }
+       clearPart(kv)
+       return kv, nil
+}
+
+//Update will update a key value record
+func (s *Service) Update(ctx context.Context, kv *model.KVDoc) (*model.KVDoc, 
error) {
+       ctx, _ = context.WithTimeout(ctx, session.Timeout)
+       oldKV, err := s.Get(ctx, kv.Domain, kv.Project, kv.ID)
+       if err != nil {
+               return nil, err
+       }
+       oldKV.Status = kv.Status
+       oldKV.Value = kv.Value
+       err = updateKeyValue(ctx, oldKV)
+       if err != nil {
+               return nil, err
+       }
+       clearPart(oldKV)
+       return oldKV, nil
+
+}
+
 //CreateOrUpdate will create or update a key value record
 //it first check label exists or not, and create labels if labels is first 
posted.
 //if label exists, then get its latest revision, and update current revision,
@@ -66,7 +128,7 @@ func (s *Service) CreateOrUpdate(ctx context.Context, kv 
*model.KVDoc) (*model.K
                        }
                        l, err = label.CreateLabel(ctx, l)
                        if err != nil {
-                               openlogging.Error("create label failed", 
openlogging.WithTags(openlogging.Tags{
+                               openlogging.Error(MsgCreateLabelFailed, 
openlogging.WithTags(openlogging.Tags{
                                        "k":      kv.Key,
                                        "domain": kv.Domain,
                                }))
@@ -143,15 +205,6 @@ func (s *Service) Exist(ctx context.Context, domain, key 
string, project string,
 //domain=tenant
 func (s *Service) Delete(ctx context.Context, kvID string, domain string, 
project string) error {
        ctx, _ = context.WithTimeout(context.Background(), session.Timeout)
-       if domain == "" {
-               return session.ErrMissingDomain
-       }
-       if project == "" {
-               return session.ErrMissingProject
-       }
-       if kvID == "" {
-               return errors.New("key id is empty")
-       }
        //delete kv
        err := deleteKV(ctx, kvID, project, domain)
        if err != nil {
@@ -194,7 +247,7 @@ func (s *Service) List(ctx context.Context, domain, project 
string, options ...s
 }
 
 //Get get kvs by id
-func (s *Service) Get(ctx context.Context, domain, project, id string, options 
...service.FindOption) (*model.KVResponse, error) {
+func (s *Service) Get(ctx context.Context, domain, project, id string, options 
...service.FindOption) (*model.KVDoc, error) {
        opts := service.FindOptions{}
        for _, o := range options {
                o(&opts)
@@ -202,14 +255,5 @@ func (s *Service) Get(ctx context.Context, domain, 
project, id string, options .
        if opts.Timeout == 0 {
                opts.Timeout = session.DefaultTimeout
        }
-       if domain == "" {
-               return nil, session.ErrMissingDomain
-       }
-       if project == "" {
-               return nil, session.ErrMissingProject
-       }
-       if id == "" {
-               return nil, session.ErrIDIsNil
-       }
-       return findKVByID(ctx, domain, project, id)
+       return findKVDocByID(ctx, domain, project, id)
 }
diff --git a/server/service/mongo/kv/kv_test.go 
b/server/service/mongo/kv/kv_test.go
index f43888d..236d8ad 100644
--- a/server/service/mongo/kv/kv_test.go
+++ b/server/service/mongo/kv/kv_test.go
@@ -28,7 +28,8 @@ import (
        "testing"
 )
 
-//
+var id string
+
 func TestService_CreateOrUpdate(t *testing.T) {
        var err error
        config.Configurations = &config.Config{DB: config.DB{URI: 
"mongodb://kie:[email protected]:27017/kie"}}
@@ -101,30 +102,57 @@ func TestService_CreateOrUpdate(t *testing.T) {
 
 }
 
-func TestService_Delete(t *testing.T) {
+func TestService_Create(t *testing.T) {
        kvsvc := &kv.Service{}
-       t.Run("delete key by kvID", func(t *testing.T) {
-               kv1, err := kvsvc.CreateOrUpdate(context.Background(), 
&model.KVDoc{
+       t.Run("create kv timeout,with labels app and service", func(t 
*testing.T) {
+               result, err := kvsvc.Create(context.TODO(), &model.KVDoc{
                        Key:   "timeout",
-                       Value: "20s",
+                       Value: "2s",
                        Labels: map[string]string{
-                               "env": "test",
+                               "app":     "mall",
+                               "service": "utCart",
                        },
                        Domain:  "default",
                        Project: "kv-test",
                })
                assert.NoError(t, err)
+               assert.NotEmpty(t, result.ID)
+               assert.Equal(t, "2s", result.Value)
+               id = result.ID
+       })
+       t.Run("create the same kv", func(t *testing.T) {
+               _, err := kvsvc.Create(context.TODO(), &model.KVDoc{
+                       Key:   "timeout",
+                       Value: "2s",
+                       Labels: map[string]string{
+                               "app":     "mall",
+                               "service": "utCart",
+                       },
+                       Domain:  "default",
+                       Project: "kv-test",
+               })
+               assert.EqualError(t, err, session.ErrKVAlreadyExists.Error())
+       })
+}
 
-               err = kvsvc.Delete(context.TODO(), kv1.ID, "default", "kv-test")
+func TestService_Update(t *testing.T) {
+       kvsvc := &kv.Service{}
+       t.Run("update kv by kvID", func(t *testing.T) {
+               result, err := kvsvc.Update(context.TODO(), &model.KVDoc{
+                       ID:      id,
+                       Value:   "3s",
+                       Domain:  "default",
+                       Project: "kv-test",
+               })
                assert.NoError(t, err)
-
+               assert.Equal(t, "3s", result.Value)
        })
-       t.Run("miss id", func(t *testing.T) {
-               err := kvsvc.Delete(context.TODO(), "", "default", "kv-test")
-               assert.Error(t, err)
-       })
-       t.Run("miss domain", func(t *testing.T) {
-               err := kvsvc.Delete(context.TODO(), "2", "", "kv-test")
-               assert.Equal(t, session.ErrMissingDomain, err)
+}
+
+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")
+               assert.NoError(t, err)
        })
 }
diff --git a/server/service/mongo/kv/tool.go b/server/service/mongo/kv/tool.go
index de9d0e3..776eea6 100644
--- a/server/service/mongo/kv/tool.go
+++ b/server/service/mongo/kv/tool.go
@@ -18,28 +18,11 @@
 package kv
 
 import (
-       "context"
        "github.com/apache/servicecomb-kie/pkg/model"
 )
 
-//clearAll clean attr which don't need to return to client side
-func clearAll(kv *model.KVDoc) {
-       clearPart(kv)
-       kv.Labels = nil
-       kv.LabelID = ""
-}
+//clearPart remove domain and project of kv
 func clearPart(kv *model.KVDoc) {
        kv.Domain = ""
        kv.Project = ""
 }
-
-func findKVByID(ctx context.Context, domain, project, kvID string) 
(*model.KVResponse, error) {
-       kv, err := findKVDocByID(ctx, domain, project, kvID)
-       if err != nil {
-               return nil, err
-       }
-       return &model.KVResponse{
-               Total: 1,
-               Data:  []*model.KVDoc{kv},
-       }, nil
-}
diff --git a/server/service/mongo/session/session.go 
b/server/service/mongo/session/session.go
index 48b5da2..0446c30 100644
--- a/server/service/mongo/session/session.go
+++ b/server/service/mongo/session/session.go
@@ -62,8 +62,10 @@ var (
        ErrKeyMustNotEmpty = errors.New("must supply key if you want to get 
exact one result")
 
        ErrIDIsNil                = errors.New("id is empty")
+       ErrKeyIsNil               = errors.New("key must not be empty")
        ErrKvIDAndLabelIDNotMatch = errors.New("kvID and labelID do not match")
        ErrRootCAMissing          = errors.New("rootCAFile is empty in config 
file")
+       ErrKVAlreadyExists        = errors.New("kv already exists")
 
        ErrViewCreation = errors.New("can not create view")
        ErrViewUpdate   = errors.New("can not update view")
diff --git a/server/service/service.go b/server/service/service.go
index d7fa613..476486b 100644
--- a/server/service/service.go
+++ b/server/service/service.go
@@ -43,11 +43,13 @@ var (
 //KV provide api of KV entity
 type KV interface {
        //below 3 methods is usually for admin console
+       Create(ctx context.Context, kv *model.KVDoc) (*model.KVDoc, error)
+       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
        //Get return kv by id
-       Get(ctx context.Context, domain, project, id string, options 
...FindOption) (*model.KVResponse, error)
+       Get(ctx context.Context, domain, project, id string, options 
...FindOption) (*model.KVDoc, error)
 }
 
 //History provide api of History entity

Reply via email to