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 2019/08/14 08:01:43 UTC

[servicecomb-kie] branch master updated: add project in uri path, modify related api to support the change (#25)

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 ad07721  add project in uri path, modify related api to support the change (#25)
ad07721 is described below

commit ad07721aa1124f6952a9733e7cab6317e63f4e6c
Author: Qi Liu <30...@users.noreply.github.com>
AuthorDate: Wed Aug 14 16:01:39 2019 +0800

    add project in uri path, modify related api to support the change (#25)
    
    * change rest api from '/v1/xxx' to '/v1/kie/xxx', add project field in label schema
    
    * add project in uri path, modify related api to support change
    1. add project field in KVDoc and LabelDoc, add the param for project in backend api
    2. all related mongo CRUD should add filter for project
    
    * remove project method, use existing methods
    
    * add a option in client side for project
    
    * remove mutiple read project
---
 client/client.go                            | 35 +++++++++++----
 client/client_test.go                       | 31 ++++++++++---
 client/options.go                           | 31 ++++++++++++-
 pkg/model/mongodb_doc.go                    |  2 +
 server/db/db.go                             |  8 ++--
 server/resource/v1/doc_struct.go            |  7 ++-
 server/resource/v1/history_resource.go      |  4 +-
 server/resource/v1/history_resource_test.go |  4 +-
 server/resource/v1/kv_resource.go           | 43 +++++++++++++-----
 server/resource/v1/kv_resource_test.go      |  2 +-
 server/service/history/service.go           |  2 +-
 server/service/kv/dao.go                    | 13 +++---
 server/service/kv/kv_test.go                | 41 ++++++++---------
 server/service/kv/service.go                | 45 +++++++++++--------
 server/service/label/dao.go                 | 20 ++++++---
 server/service/label/service.go             |  7 +--
 swagger/servicecomb-kie.yaml                | 68 +++++++++++++++++++++++++++--
 17 files changed, 267 insertions(+), 96 deletions(-)

diff --git a/client/client.go b/client/client.go
index c1975ff..cfa41bb 100644
--- a/client/client.go
+++ b/client/client.go
@@ -35,7 +35,8 @@ import (
 
 //const
 const (
-	APIPathKV = "v1/kv"
+	version   = "v1"
+	APIPathKV = "kie/kv"
 )
 
 //client errors
@@ -81,8 +82,15 @@ func New(config Config) (*Client, error) {
 }
 
 //Put create value of a key
-func (c *Client) Put(ctx context.Context, kv model.KVDoc) (*model.KVDoc, error) {
-	url := fmt.Sprintf("%s/%s/%s", c.opts.Endpoint, APIPathKV, kv.Key)
+func (c *Client) Put(ctx context.Context, kv model.KVDoc, opts ...OpOption) (*model.KVDoc, error) {
+	options := OpOptions{}
+	for _, o := range opts {
+		o(&options)
+	}
+	if options.Project == "" {
+		options.Project = defaultProject
+	}
+	url := fmt.Sprintf("%s/%s/%s/%s/%s", c.opts.Endpoint, version, options.Project, APIPathKV, kv.Key)
 	h := http.Header{}
 	h.Set("Content-Type", "application/json")
 	body, _ := json.Marshal(kv)
@@ -118,7 +126,10 @@ func (c *Client) Get(ctx context.Context, key string, opts ...GetOption) ([]*mod
 	for _, o := range opts {
 		o(&options)
 	}
-	url := fmt.Sprintf("%s/%s/%s", c.opts.Endpoint, APIPathKV, key)
+	if options.Project == "" {
+		options.Project = defaultProject
+	}
+	url := fmt.Sprintf("%s/%s/%s/%s/%s", c.opts.Endpoint, version, options.Project, APIPathKV, key)
 	h := http.Header{}
 	resp, err := c.c.HTTPDoWithContext(ctx, "GET", url, h, nil)
 	if err != nil {
@@ -136,9 +147,8 @@ func (c *Client) Get(ctx context.Context, key string, opts ...GetOption) ([]*mod
 		}))
 		return nil, fmt.Errorf("get %s failed,http status [%s], body [%s]", key, resp.Status, b)
 	}
-
-	kvs := make([]*model.KVDoc, 0)
-	err = json.Unmarshal(b, kvs)
+	var kvs []*model.KVDoc
+	err = json.Unmarshal(b, &kvs)
 	if err != nil {
 		openlogging.Error("unmarshal kv failed:" + err.Error())
 		return nil, err
@@ -147,8 +157,15 @@ func (c *Client) Get(ctx context.Context, key string, opts ...GetOption) ([]*mod
 }
 
 //Delete remove kv
-func (c *Client) Delete(ctx context.Context, kvID, labelID string) error {
-	url := fmt.Sprintf("%s/%s/?kvID=%s", c.opts.Endpoint, APIPathKV, kvID)
+func (c *Client) Delete(ctx context.Context, kvID, labelID string, opts ...OpOption) error {
+	options := OpOptions{}
+	for _, o := range opts {
+		o(&options)
+	}
+	if options.Project == "" {
+		options.Project = defaultProject
+	}
+	url := fmt.Sprintf("%s/%s/%s/%s/?kvID=%s", c.opts.Endpoint, version, options.Project, APIPathKV, kvID)
 	if labelID != "" {
 		url = fmt.Sprintf("%s?labelID=%s", url, labelID)
 	}
diff --git a/client/client_test.go b/client/client_test.go
index ff29eae..8883a32 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -47,7 +47,7 @@ var _ = Describe("Client", func() {
 	})
 
 	Describe("get ", func() {
-		Context("only by key", func() {
+		Context("only by key with default project", func() {
 			_, err := c1.Get(context.TODO(), "app.properties")
 			It("should be 404 error", func() {
 				Expect(err).Should(Equal(ErrKeyNotExist))
@@ -63,9 +63,16 @@ var _ = Describe("Client", func() {
 			})
 
 		})
+
+		Context("by project", func() {
+			_, err := c1.Get(context.TODO(), "app.properties", WithGetProject("test"))
+			It("should be 404 error", func() {
+				Expect(err).Should(Equal(ErrKeyNotExist))
+			})
+		})
 	})
 
-	Describe("put /v1/kv/{key}", func() {
+	Describe("put /v1/{project}/kie/kv/{key}", func() {
 		Context("create or update key value", func() {
 			c1, _ = New(Config{
 				Endpoint: "http://127.0.0.1:30110",
@@ -75,7 +82,7 @@ var _ = Describe("Client", func() {
 				Labels: map[string]string{"service": "tester"},
 				Value:  "1s",
 			}
-			res, err := c1.Put(context.TODO(), kv)
+			res, err := c1.Put(context.TODO(), kv, WithProject("test"))
 			It("should not be error", func() {
 				Expect(err).Should(BeNil())
 			})
@@ -83,11 +90,16 @@ var _ = Describe("Client", func() {
 				Expect(res.Key).Should(Equal(kv.Key))
 				Expect(res.Labels).Should(Equal(kv.Labels))
 				Expect(res.Value).Should(Equal(kv.Value))
+				Expect(res.Project).Should(Equal("test"))
+			})
+			kvs, _ := c1.Get(context.TODO(), "app.properties", WithGetProject("test"))
+			It("should exactly 1 kv", func() {
+				Expect(len(kvs)).Should(Equal(1))
 			})
 		})
 	})
 
-	Describe("DELETE /v1/kv/", func() {
+	Describe("DELETE /v1/{project}/kie/kv/", func() {
 		Context("by kvID", func() {
 			client2, err := New(Config{
 				Endpoint: "http://127.0.0.1:30110",
@@ -97,18 +109,25 @@ var _ = Describe("Client", func() {
 			kvBody.Key = "time"
 			kvBody.Value = "100s"
 			kvBody.ValueType = "string"
+			kvBody.Project = "test"
 			kvBody.Labels = make(map[string]string)
 			kvBody.Labels["evn"] = "test"
-			kv, err := client2.Put(context.TODO(), kvBody)
+			kv, err := client2.Put(context.TODO(), kvBody, WithProject("test"))
 			It("should not be error", func() {
 				Ω(err).ShouldNot(HaveOccurred())
 				Expect(kv.Key).To(Equal(kvBody.Key))
+				Expect(kv.Project).To(Equal(kvBody.Project))
+			})
+			kvs, err := client2.Get(context.TODO(), "time", WithGetProject("test"))
+			It("should return exactly 1 kv", func() {
+				Expect(len(kvs)).Should(Equal(1))
+				Expect(err).Should(BeNil())
 			})
 			client3, err := New(Config{
 				Endpoint: "http://127.0.0.1:30110",
 			})
 			It("should be 204", func() {
-				err := client3.Delete(context.TODO(), kv.ID.Hex(), "")
+				err := client3.Delete(context.TODO(), kv.ID.Hex(), "", WithProject("test"))
 				Ω(err).ShouldNot(HaveOccurred())
 			})
 		})
diff --git a/client/options.go b/client/options.go
index 722175d..cda9f52 100644
--- a/client/options.go
+++ b/client/options.go
@@ -17,13 +17,26 @@
 
 package client
 
+const (
+	defaultProject = "default"
+)
+
 //GetOption is the functional option of client func
 type GetOption func(*GetOptions)
 
+//OpOption is the functional option of client func
+type OpOption func(*OpOptions)
+
 //GetOptions is the options of client func
 type GetOptions struct {
-	Labels map[string]string
-	Depth  int
+	Labels  map[string]string
+	Depth   int
+	Project string
+}
+
+//OpOptions is the options of client func
+type OpOptions struct {
+	Project string
 }
 
 //WithLabels query kv by labels
@@ -39,3 +52,17 @@ func WithDepth(d int) GetOption {
 		options.Depth = d
 	}
 }
+
+//WithGetProject query keys with certain project
+func WithGetProject(project string) GetOption {
+	return func(options *GetOptions) {
+		options.Project = project
+	}
+}
+
+//WithProject set project to param
+func WithProject(project string) OpOption {
+	return func(options *OpOptions) {
+		options.Project = project
+	}
+}
diff --git a/pkg/model/mongodb_doc.go b/pkg/model/mongodb_doc.go
index 07397ff..5e89975 100644
--- a/pkg/model/mongodb_doc.go
+++ b/pkg/model/mongodb_doc.go
@@ -25,6 +25,7 @@ type LabelDoc struct {
 	Labels   map[string]string  `json:"labels,omitempty"`
 	Revision int                `json:"revision,omitempty"`
 	Domain   string             `json:"domain,omitempty"` //tenant info
+	Project  string             `json:"project,omitempty"`
 }
 
 //KVDoc is database struct to store kv
@@ -39,6 +40,7 @@ type KVDoc struct {
 	Labels   map[string]string `json:"labels,omitempty"` //redundant
 	Domain   string            `json:"domain,omitempty"` //redundant
 	Revision int               `json:"revision,omitempty" bson:"-"`
+	Project  string            `json:"project,omitempty"`
 }
 
 //LabelRevisionDoc is database struct to store label history stats
diff --git a/server/db/db.go b/server/db/db.go
index d592d0d..93c82b7 100644
--- a/server/db/db.go
+++ b/server/db/db.go
@@ -24,13 +24,14 @@ import (
 	"crypto/x509"
 	"errors"
 	"fmt"
+	"io/ioutil"
+	"sync"
+	"time"
+
 	"github.com/apache/servicecomb-kie/server/config"
 	"github.com/go-mesh/openlogging"
 	"go.mongodb.org/mongo-driver/mongo"
 	"go.mongodb.org/mongo-driver/mongo/options"
-	"io/ioutil"
-	"sync"
-	"time"
 )
 
 //const for db name and collection name
@@ -47,6 +48,7 @@ const (
 //db errors
 var (
 	ErrMissingDomain          = errors.New("domain info missing, illegal access")
+	ErrMissingProject         = errors.New("project info missing, illegal access")
 	ErrKeyNotExists           = errors.New("key with labels does not exits")
 	ErrLabelNotExists         = errors.New("labels does not exits")
 	ErrTooMany                = errors.New("key with labels should be only one")
diff --git a/server/resource/v1/doc_struct.go b/server/resource/v1/doc_struct.go
index 371ed63..f5a92be 100644
--- a/server/resource/v1/doc_struct.go
+++ b/server/resource/v1/doc_struct.go
@@ -36,7 +36,7 @@ var (
 		Name:      common.QueryParamQ,
 		ParamType: goRestful.QueryParameterKind,
 		Desc: "the combination format is {label_key}:{label_value}+{label_key}:{label_value} " +
-			"for example: /v1/kv?q=app:mall&q=app:mall+service:cart " +
+			"for example: /v1/test/kie/kv?q=app:mall&q=app:mall+service:cart " +
 			"that will query key values from 2 kinds of labels",
 	}
 	DocPathKey = &restful.Parameters{
@@ -44,6 +44,11 @@ var (
 		Name:      "key",
 		ParamType: goRestful.PathParameterKind,
 	}
+	DocPathProject = &restful.Parameters{
+		DataType:  "string",
+		Name:      "project",
+		ParamType: goRestful.PathParameterKind,
+	}
 	DocPathLabelID = &restful.Parameters{
 		DataType:  "string",
 		Name:      "label_id",
diff --git a/server/resource/v1/history_resource.go b/server/resource/v1/history_resource.go
index 2a2fc3c..73b237a 100644
--- a/server/resource/v1/history_resource.go
+++ b/server/resource/v1/history_resource.go
@@ -66,11 +66,11 @@ func (r *HistoryResource) URLPatterns() []restful.Route {
 	return []restful.Route{
 		{
 			Method:           http.MethodGet,
-			Path:             "/v1/revision/{label_id}",
+			Path:             "/v1/{project}/kie/revision/{label_id}",
 			ResourceFuncName: "GetRevisionsByLabelID",
 			FuncDesc:         "get all revisions by label id",
 			Parameters: []*restful.Parameters{
-				DocPathLabelID,
+				DocPathProject, DocPathLabelID,
 			},
 			Returns: []*restful.Returns{
 				{
diff --git a/server/resource/v1/history_resource_test.go b/server/resource/v1/history_resource_test.go
index aacdf54..c4756aa 100644
--- a/server/resource/v1/history_resource_test.go
+++ b/server/resource/v1/history_resource_test.go
@@ -60,8 +60,8 @@ var _ = Describe("v1 history resource", func() {
 					"test": "revisions",
 				},
 			}
-			kv, _ = kvsvc.CreateOrUpdate(context.Background(), "default", kv)
-			path := fmt.Sprintf("/v1/revision/%s", kv.LabelID)
+			kv, _ = kvsvc.CreateOrUpdate(context.Background(), "default", kv, "test")
+			path := fmt.Sprintf("/v1/%s/kie/revision/%s", "test", kv.LabelID)
 			r, _ := http.NewRequest("GET", path, nil)
 			revision := &v1.HistoryResource{}
 			chain, _ := handler.GetChain(common.Provider, "")
diff --git a/server/resource/v1/kv_resource.go b/server/resource/v1/kv_resource.go
index 49ed48a..deba25a 100644
--- a/server/resource/v1/kv_resource.go
+++ b/server/resource/v1/kv_resource.go
@@ -39,6 +39,11 @@ type KVResource struct {
 func (r *KVResource) Put(context *restful.Context) {
 	var err error
 	key := context.ReadPathParameter("key")
+	project := context.ReadPathParameter("project")
+	if project == "" {
+		WriteErrResponse(context, http.StatusForbidden, "project must not be empty", common.ContentTypeText)
+		return
+	}
 	kv := new(model.KVDoc)
 	decoder := json.NewDecoder(context.ReadRequest().Body)
 	if err = decoder.Decode(kv); err != nil {
@@ -50,7 +55,7 @@ func (r *KVResource) Put(context *restful.Context) {
 		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
 	}
 	kv.Key = key
-	kv, err = kvsvc.CreateOrUpdate(context.Ctx, domain.(string), kv)
+	kv, err = kvsvc.CreateOrUpdate(context.Ctx, domain.(string), kv, project)
 	if err != nil {
 		ErrLog("put", kv, err)
 		WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
@@ -70,6 +75,11 @@ func (r *KVResource) GetByKey(context *restful.Context) {
 		WriteErrResponse(context, http.StatusForbidden, "key must not be empty", common.ContentTypeText)
 		return
 	}
+	project := context.ReadPathParameter("project")
+	if project == "" {
+		WriteErrResponse(context, http.StatusForbidden, "project must not be empty", common.ContentTypeText)
+		return
+	}
 	values := context.ReadRequest().URL.Query()
 	labels := make(map[string]string, len(values))
 	for k, v := range values {
@@ -89,7 +99,7 @@ func (r *KVResource) GetByKey(context *restful.Context) {
 		WriteErrResponse(context, http.StatusBadRequest, MsgIllegalDepth, common.ContentTypeText)
 		return
 	}
-	kvs, err := kvsvc.FindKV(context.Ctx, domain.(string),
+	kvs, err := kvsvc.FindKV(context.Ctx, domain.(string), project,
 		kvsvc.WithKey(key), kvsvc.WithLabels(labels), kvsvc.WithDepth(d))
 	if err != nil {
 		if err == db.ErrKeyNotExists {
@@ -114,6 +124,11 @@ func (r *KVResource) SearchByLabels(context *restful.Context) {
 		WriteErrResponse(context, http.StatusBadRequest, err.Error(), common.ContentTypeText)
 		return
 	}
+	project := context.ReadPathParameter("project")
+	if project == "" {
+		WriteErrResponse(context, http.StatusForbidden, "project must not be empty", common.ContentTypeText)
+		return
+	}
 	domain := ReadDomain(context)
 	if domain == nil {
 		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
@@ -124,7 +139,7 @@ func (r *KVResource) SearchByLabels(context *restful.Context) {
 		openlogging.Debug("find by combination", openlogging.WithTags(openlogging.Tags{
 			"q": labels,
 		}))
-		result, err := kvsvc.FindKV(context.Ctx, domain.(string), kvsvc.WithLabels(labels))
+		result, err := kvsvc.FindKV(context.Ctx, domain.(string), project, kvsvc.WithLabels(labels))
 		if err != nil {
 			if err == db.ErrKeyNotExists {
 				continue
@@ -153,6 +168,11 @@ func (r *KVResource) SearchByLabels(context *restful.Context) {
 
 //Delete deletes key by ids
 func (r *KVResource) Delete(context *restful.Context) {
+	project := context.ReadPathParameter("project")
+	if project == "" {
+		WriteErrResponse(context, http.StatusForbidden, "project must not be empty", common.ContentTypeText)
+		return
+	}
 	domain := ReadDomain(context)
 	if domain == nil {
 		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
@@ -163,7 +183,7 @@ func (r *KVResource) Delete(context *restful.Context) {
 		return
 	}
 	labelID := context.ReadQueryParameter("labelID")
-	err := kvsvc.Delete(kvID, labelID, domain.(string))
+	err := kvsvc.Delete(kvID, labelID, domain.(string), project)
 	if err != nil {
 		openlogging.Error("delete failed ,", openlogging.WithTags(openlogging.Tags{
 			"kvID":    kvID,
@@ -181,11 +201,11 @@ func (r *KVResource) URLPatterns() []restful.Route {
 	return []restful.Route{
 		{
 			Method:           http.MethodPut,
-			Path:             "/v1/kv/{key}",
+			Path:             "/v1/{project}/kie/kv/{key}",
 			ResourceFuncName: "Put",
 			FuncDesc:         "create or update key value",
 			Parameters: []*restful.Parameters{
-				DocPathKey, {
+				DocPathProject, DocPathKey, {
 					DataType:  "string",
 					Name:      "X-Realm",
 					ParamType: goRestful.HeaderParameterKind,
@@ -203,11 +223,11 @@ func (r *KVResource) URLPatterns() []restful.Route {
 			Read:     KVBody{},
 		}, {
 			Method:           http.MethodGet,
-			Path:             "/v1/kv/{key}",
+			Path:             "/v1/{project}/kie/kv/{key}",
 			ResourceFuncName: "GetByKey",
 			FuncDesc:         "get key values by key and labels",
 			Parameters: []*restful.Parameters{
-				DocPathKey, DocHeaderDepth,
+				DocPathProject, DocPathKey, DocHeaderDepth,
 			},
 			Returns: []*restful.Returns{
 				{
@@ -221,11 +241,11 @@ func (r *KVResource) URLPatterns() []restful.Route {
 			Read:     &KVBody{},
 		}, {
 			Method:           http.MethodGet,
-			Path:             "/v1/kv",
+			Path:             "/v1/{project}/kie/kv",
 			ResourceFuncName: "SearchByLabels",
 			FuncDesc:         "search key values by labels combination",
 			Parameters: []*restful.Parameters{
-				DocQueryCombination,
+				DocPathProject, DocQueryCombination,
 			},
 			Returns: []*restful.Returns{
 				{
@@ -238,11 +258,12 @@ func (r *KVResource) URLPatterns() []restful.Route {
 			Produces: []string{goRestful.MIME_JSON},
 		}, {
 			Method:           http.MethodDelete,
-			Path:             "/v1/kv/",
+			Path:             "/v1/{project}/kie/kv/",
 			ResourceFuncName: "Delete",
 			FuncDesc: "Delete key by kvID and labelID,If the labelID is nil, query the collection kv to get it." +
 				"It means if only get kvID, it can also delete normally.But if you want better performance, you need to pass the labelID",
 			Parameters: []*restful.Parameters{
+				DocPathProject,
 				kvIDParameters,
 				labelIDParameters,
 			},
diff --git a/server/resource/v1/kv_resource_test.go b/server/resource/v1/kv_resource_test.go
index bba7d12..afe9a25 100644
--- a/server/resource/v1/kv_resource_test.go
+++ b/server/resource/v1/kv_resource_test.go
@@ -56,7 +56,7 @@ var _ = Describe("v1 kv resource", func() {
 				Labels: map[string]string{"service": "tester"},
 			}
 			j, _ := json.Marshal(kv)
-			r, _ := http.NewRequest("PUT", "/v1/kv/timeout", bytes.NewBuffer(j))
+			r, _ := http.NewRequest("PUT", "/v1/test/kie/kv/timeout", bytes.NewBuffer(j))
 			noopH := &noop.NoopAuthHandler{}
 			chain, _ := handler.CreateChain(common.Provider, "testchain1", noopH.Name())
 			r.Header.Set("Content-Type", "application/json")
diff --git a/server/service/history/service.go b/server/service/history/service.go
index 1de7695..3a78dae 100644
--- a/server/service/history/service.go
+++ b/server/service/history/service.go
@@ -30,7 +30,7 @@ import (
 
 //GetAndAddHistory get latest labels revision and call AddHistory
 func GetAndAddHistory(ctx context.Context,
-	labelID string, labels map[string]string, kvs []*model.KVDoc, domain string) (int, error) {
+	labelID string, labels map[string]string, kvs []*model.KVDoc, domain string, project string) (int, error) {
 	ctx, cancel := context.WithTimeout(ctx, db.Timeout)
 	defer cancel()
 	r, err := label.GetLatestLabel(ctx, labelID)
diff --git a/server/service/kv/dao.go b/server/service/kv/dao.go
index 0307b45..f35fb95 100644
--- a/server/service/kv/dao.go
+++ b/server/service/kv/dao.go
@@ -20,6 +20,7 @@ package kvsvc
 import (
 	"context"
 	"fmt"
+
 	"github.com/apache/servicecomb-kie/pkg/model"
 	"github.com/apache/servicecomb-kie/server/db"
 	"github.com/apache/servicecomb-kie/server/service/history"
@@ -64,7 +65,7 @@ func createKey(ctx context.Context, kv *model.KVDoc) (*model.KVDoc, error) {
 	if err != nil && err != db.ErrKeyNotExists {
 		return nil, err
 	}
-	revision, err := history.GetAndAddHistory(ctx, kv.LabelID, kv.Labels, kvs, kv.Domain)
+	revision, err := history.GetAndAddHistory(ctx, kv.LabelID, kv.Labels, kvs, kv.Domain, kv.Project)
 	if err != nil {
 		openlogging.Warn(
 			fmt.Sprintf("can not updateKeyValue version for [%s] [%s] in [%s]",
@@ -102,7 +103,7 @@ func updateKeyValue(ctx context.Context, kv *model.KVDoc) (int, error) {
 	if err != nil && err != db.ErrKeyNotExists {
 		return 0, err
 	}
-	revision, err := history.GetAndAddHistory(ctx, kv.LabelID, kv.Labels, kvs, kv.Domain)
+	revision, err := history.GetAndAddHistory(ctx, kv.LabelID, kv.Labels, kvs, kv.Domain, kv.Project)
 	if err != nil {
 		openlogging.Warn(
 			fmt.Sprintf("can not label revision for [%s] [%s] in [%s],err: %s",
@@ -115,14 +116,14 @@ func updateKeyValue(ctx context.Context, kv *model.KVDoc) (int, error) {
 
 }
 
-func findKV(ctx context.Context, domain string, opts FindOptions) (*mongo.Cursor, error) {
+func findKV(ctx context.Context, domain string, project string, opts FindOptions) (*mongo.Cursor, error) {
 	c, err := db.GetClient()
 	if err != nil {
 		return nil, err
 	}
 	collection := c.Database(db.Name).Collection(db.CollectionKV)
 	ctx, _ = context.WithTimeout(ctx, opts.Timeout)
-	filter := bson.M{"domain": domain}
+	filter := bson.M{"domain": domain, "project": project}
 	if opts.Key != "" {
 		filter["key"] = opts.Key
 	}
@@ -165,13 +166,13 @@ func findOneKey(ctx context.Context, filter bson.M) ([]*model.KVDoc, error) {
 }
 
 //DeleteKV by kvID
-func DeleteKV(ctx context.Context, hexID primitive.ObjectID) error {
+func DeleteKV(ctx context.Context, hexID primitive.ObjectID, project string) error {
 	c, err := db.GetClient()
 	if err != nil {
 		return err
 	}
 	collection := c.Database(db.Name).Collection(db.CollectionKV)
-	dr, err := collection.DeleteOne(ctx, bson.M{"_id": hexID})
+	dr, err := collection.DeleteOne(ctx, bson.M{"_id": hexID, "project": project})
 	//check error and delete number
 	if err != nil {
 		openlogging.Error(fmt.Sprintf("delete [%s] failed : [%s]", hexID, err))
diff --git a/server/service/kv/kv_test.go b/server/service/kv/kv_test.go
index 61db673..1d5287c 100644
--- a/server/service/kv/kv_test.go
+++ b/server/service/kv/kv_test.go
@@ -19,6 +19,7 @@ package kvsvc_test
 
 import (
 	"context"
+
 	"github.com/apache/servicecomb-kie/pkg/model"
 	"github.com/apache/servicecomb-kie/server/config"
 	"github.com/apache/servicecomb-kie/server/db"
@@ -46,7 +47,7 @@ var _ = Describe("Kv mongodb service", func() {
 					"app":     "mall",
 					"service": "cart",
 				},
-			})
+			}, "test")
 			It("should not return err", func() {
 				Expect(err).Should(BeNil())
 			})
@@ -64,8 +65,8 @@ var _ = Describe("Kv mongodb service", func() {
 					"service": "cart",
 					"version": "1.0.0",
 				},
-			})
-			oid, err := kvsvc.KVExist(context.TODO(), "default", "timeout", kvsvc.WithLabels(map[string]string{
+			}, "test")
+			oid, err := kvsvc.KVExist(context.TODO(), "default", "timeout", "test", kvsvc.WithLabels(map[string]string{
 				"app":     "mall",
 				"service": "cart",
 				"version": "1.0.0",
@@ -88,11 +89,11 @@ var _ = Describe("Kv mongodb service", func() {
 				Labels: map[string]string{
 					"app": "mall",
 				},
-			})
+			}, "test")
 			It("should not return err", func() {
 				Expect(err).Should(BeNil())
 			})
-			kvs1, err := kvsvc.FindKV(context.Background(), "default", kvsvc.WithKey("timeout"), kvsvc.WithLabels(map[string]string{
+			kvs1, err := kvsvc.FindKV(context.Background(), "default", "test", kvsvc.WithKey("timeout"), kvsvc.WithLabels(map[string]string{
 				"app": "mall",
 			}), kvsvc.WithExactLabels())
 			It("should be 1s", func() {
@@ -105,17 +106,17 @@ var _ = Describe("Kv mongodb service", func() {
 				Labels: map[string]string{
 					"app": "mall",
 				},
-			})
+			}, "test")
 			It("should has same id", func() {
 				Expect(afterKV.ID.Hex()).Should(Equal(beforeKV.ID.Hex()))
 			})
-			oid, err := kvsvc.KVExist(context.Background(), "default", "timeout", kvsvc.WithLabels(map[string]string{
+			oid, err := kvsvc.KVExist(context.Background(), "default", "timeout", "test", kvsvc.WithLabels(map[string]string{
 				"app": "mall",
 			}))
 			It("should exists", func() {
 				Expect(oid.Hex()).Should(Equal(beforeKV.ID.Hex()))
 			})
-			kvs, err := kvsvc.FindKV(context.Background(), "default", kvsvc.WithKey("timeout"), kvsvc.WithLabels(map[string]string{
+			kvs, err := kvsvc.FindKV(context.Background(), "default", "test", kvsvc.WithKey("timeout"), kvsvc.WithLabels(map[string]string{
 				"app": "mall",
 			}), kvsvc.WithExactLabels())
 			It("should be 3s", func() {
@@ -126,7 +127,7 @@ var _ = Describe("Kv mongodb service", func() {
 
 	Describe("greedy find by kv and labels", func() {
 		Context("with labels app,depth is 1 ", func() {
-			kvs, err := kvsvc.FindKV(context.Background(), "default",
+			kvs, err := kvsvc.FindKV(context.Background(), "default", "test",
 				kvsvc.WithKey("timeout"), kvsvc.WithLabels(map[string]string{
 					"app": "mall",
 				}))
@@ -139,7 +140,7 @@ var _ = Describe("Kv mongodb service", func() {
 
 		})
 		Context("with labels app,depth is 2 ", func() {
-			kvs, err := kvsvc.FindKV(context.Background(), "default", kvsvc.WithKey("timeout"),
+			kvs, err := kvsvc.FindKV(context.Background(), "default", "test", kvsvc.WithKey("timeout"),
 				kvsvc.WithLabels(map[string]string{
 					"app": "mall",
 				}),
@@ -155,7 +156,7 @@ var _ = Describe("Kv mongodb service", func() {
 	})
 	Describe("exact find by kv and labels", func() {
 		Context("with labels app ", func() {
-			kvs, err := kvsvc.FindKV(context.Background(), "default", kvsvc.WithKey("timeout"), kvsvc.WithLabels(map[string]string{
+			kvs, err := kvsvc.FindKV(context.Background(), "default", "test", kvsvc.WithKey("timeout"), kvsvc.WithLabels(map[string]string{
 				"app": "mall",
 			}), kvsvc.WithExactLabels())
 			It("should not return err", func() {
@@ -169,7 +170,7 @@ var _ = Describe("Kv mongodb service", func() {
 	})
 	Describe("exact find by labels", func() {
 		Context("with labels app ", func() {
-			kvs, err := kvsvc.FindKV(context.Background(), "default", kvsvc.WithLabels(map[string]string{
+			kvs, err := kvsvc.FindKV(context.Background(), "default", "test", kvsvc.WithLabels(map[string]string{
 				"app": "mall",
 			}), kvsvc.WithExactLabels())
 			It("should not return err", func() {
@@ -183,7 +184,7 @@ var _ = Describe("Kv mongodb service", func() {
 	})
 	Describe("greedy find by labels", func() {
 		Context("with labels app ans service ", func() {
-			kvs, err := kvsvc.FindKV(context.Background(), "default", kvsvc.WithLabels(map[string]string{
+			kvs, err := kvsvc.FindKV(context.Background(), "default", "test", kvsvc.WithLabels(map[string]string{
 				"app":     "mall",
 				"service": "cart",
 			}))
@@ -205,7 +206,7 @@ var _ = Describe("Kv mongodb service", func() {
 				Labels: map[string]string{
 					"env": "test",
 				},
-			})
+			}, "test")
 			It("should not return err", func() {
 				Expect(err).Should(BeNil())
 			})
@@ -213,7 +214,7 @@ var _ = Describe("Kv mongodb service", func() {
 				Expect(err).Should(BeNil())
 			})
 
-			err = kvsvc.Delete(kv1.ID.Hex(), "", "default")
+			err = kvsvc.Delete(kv1.ID.Hex(), "", "default", "test")
 			It("should not return err", func() {
 				Expect(err).Should(BeNil())
 			})
@@ -226,7 +227,7 @@ var _ = Describe("Kv mongodb service", func() {
 				Labels: map[string]string{
 					"env": "test",
 				},
-			})
+			}, "test")
 			It("should not return err", func() {
 				Expect(err).Should(BeNil())
 			})
@@ -234,26 +235,26 @@ var _ = Describe("Kv mongodb service", func() {
 				Expect(err).Should(BeNil())
 			})
 
-			err = kvsvc.Delete(kv1.ID.Hex(), kv1.LabelID, "default")
+			err = kvsvc.Delete(kv1.ID.Hex(), kv1.LabelID, "default", "test")
 			It("should not return err", func() {
 				Expect(err).Should(BeNil())
 			})
 
 		})
 		Context("test miss kvID, no panic", func() {
-			err := kvsvc.Delete("", "", "default")
+			err := kvsvc.Delete("", "", "default", "test")
 			It("should not return err", func() {
 				Expect(err).Should(HaveOccurred())
 			})
 		})
 		Context("Test encode error ", func() {
-			err := kvsvc.Delete("12312312321", "", "default")
+			err := kvsvc.Delete("12312312321", "", "default", "test")
 			It("should return err", func() {
 				Expect(err).To(HaveOccurred())
 			})
 		})
 		Context("Test miss domain error ", func() {
-			err := kvsvc.Delete("12312312321", "", "")
+			err := kvsvc.Delete("12312312321", "", "", "test")
 			It("should return err", func() {
 				Expect(err).Should(Equal(db.ErrMissingDomain))
 			})
diff --git a/server/service/kv/service.go b/server/service/kv/service.go
index f658ce7..51497e2 100644
--- a/server/service/kv/service.go
+++ b/server/service/kv/service.go
@@ -20,6 +20,9 @@ package kvsvc
 import (
 	"context"
 	"fmt"
+	"reflect"
+	"time"
+
 	"github.com/apache/servicecomb-kie/pkg/model"
 	"github.com/apache/servicecomb-kie/server/db"
 	"github.com/apache/servicecomb-kie/server/service/history"
@@ -27,8 +30,6 @@ import (
 	"github.com/go-mesh/openlogging"
 	"go.mongodb.org/mongo-driver/bson"
 	"go.mongodb.org/mongo-driver/bson/primitive"
-	"reflect"
-	"time"
 )
 
 //MongodbService operate data in mongodb
@@ -41,7 +42,7 @@ type MongodbService struct {
 //if label exists, then get its latest revision, and update current revision,
 //save the current label and its all key values to history collection
 //then check key exists or not, then create or update it
-func CreateOrUpdate(ctx context.Context, domain string, kv *model.KVDoc) (*model.KVDoc, error) {
+func CreateOrUpdate(ctx context.Context, domain string, kv *model.KVDoc, project string) (*model.KVDoc, error) {
 	ctx, _ = context.WithTimeout(ctx, db.Timeout)
 	if domain == "" {
 		return nil, db.ErrMissingDomain
@@ -52,12 +53,13 @@ func CreateOrUpdate(ctx context.Context, domain string, kv *model.KVDoc) (*model
 		}
 	}
 
-	//check labels exits or not
-	labelID, err := label.Exist(ctx, domain, kv.Labels)
+	//check whether the projecr has certain labels or not
+	labelID, err := label.Exist(ctx, domain, project, kv.Labels)
+
 	var l *model.LabelDoc
 	if err != nil {
 		if err == db.ErrLabelNotExists {
-			l, err = label.CreateLabel(ctx, domain, kv.Labels)
+			l, err = label.CreateLabel(ctx, domain, kv.Labels, project)
 			if err != nil {
 				openlogging.Error("create label failed", openlogging.WithTags(openlogging.Tags{
 					"k":      kv.Key,
@@ -72,11 +74,12 @@ func CreateOrUpdate(ctx context.Context, domain string, kv *model.KVDoc) (*model
 
 	}
 	kv.LabelID = labelID.Hex()
+	kv.Project = project
 	kv.Domain = domain
 	if kv.ValueType == "" {
 		kv.ValueType = db.DefaultValueType
 	}
-	keyID, err := KVExist(ctx, domain, kv.Key, WithLabelID(kv.LabelID))
+	keyID, err := KVExist(ctx, domain, kv.Key, kv.Project, WithLabelID(kv.LabelID))
 	if err != nil {
 		if err == db.ErrKeyNotExists {
 			kv, err := createKey(ctx, kv)
@@ -99,20 +102,20 @@ func CreateOrUpdate(ctx context.Context, domain string, kv *model.KVDoc) (*model
 }
 
 //KVExist supports you query by label map or labels id
-func KVExist(ctx context.Context, domain, key string, options ...FindOption) (primitive.ObjectID, error) {
+func KVExist(ctx context.Context, domain, key string, project string, options ...FindOption) (primitive.ObjectID, error) {
 	ctx, _ = context.WithTimeout(context.Background(), db.Timeout)
 	opts := FindOptions{}
 	for _, o := range options {
 		o(&opts)
 	}
 	if opts.LabelID != "" {
-		kvs, err := FindKVByLabelID(ctx, domain, opts.LabelID, key)
+		kvs, err := FindKVByLabelID(ctx, domain, opts.LabelID, key, project)
 		if err != nil {
 			return primitive.NilObjectID, err
 		}
 		return kvs[0].ID, nil
 	}
-	kvs, err := FindKV(ctx, domain, WithExactLabels(), WithLabels(opts.Labels), WithKey(key))
+	kvs, err := FindKV(ctx, domain, project, WithExactLabels(), WithLabels(opts.Labels), WithKey(key))
 	if err != nil {
 		return primitive.NilObjectID, err
 	}
@@ -127,11 +130,14 @@ func KVExist(ctx context.Context, domain, key string, options ...FindOption) (pr
 //Delete delete kv,If the labelID is "", query the collection kv to get it
 //domain=tenant
 //1.delete kv;2.add history
-func Delete(kvID string, labelID string, domain string) error {
+func Delete(kvID string, labelID string, domain string, project string) error {
 	ctx, _ := context.WithTimeout(context.Background(), db.Timeout)
 	if domain == "" {
 		return db.ErrMissingDomain
 	}
+	if project == "" {
+		return db.ErrMissingProject
+	}
 	hex, err := primitive.ObjectIDFromHex(kvID)
 	if err != nil {
 		return err
@@ -139,7 +145,7 @@ func Delete(kvID string, labelID string, domain string) error {
 	//if labelID == "",get labelID by kvID
 	var kv *model.KVDoc
 	if labelID == "" {
-		kvArray, err := findOneKey(ctx, bson.M{"_id": hex})
+		kvArray, err := findOneKey(ctx, bson.M{"_id": hex, "project": project})
 		if err != nil {
 			return err
 		}
@@ -158,11 +164,11 @@ func Delete(kvID string, labelID string, domain string) error {
 		return err
 	}
 	//delete kv
-	err = DeleteKV(ctx, hex)
+	err = DeleteKV(ctx, hex, project)
 	if err != nil {
 		return err
 	}
-	kvs, err := findKeys(ctx, bson.M{"label_id": labelID}, true)
+	kvs, err := findKeys(ctx, bson.M{"label_id": labelID, "project": project}, true)
 	//Key may be empty When delete
 	if err != nil && err != db.ErrKeyNotExists {
 		return err
@@ -188,9 +194,9 @@ func Delete(kvID string, labelID string, domain string) error {
 //FindKVByLabelID get kvs by key and label id
 //key can be empty, then it will return all key values
 //if key is given, will return 0-1 key value
-func FindKVByLabelID(ctx context.Context, domain, labelID, key string) ([]*model.KVDoc, error) {
+func FindKVByLabelID(ctx context.Context, domain, labelID, key string, project string) ([]*model.KVDoc, error) {
 
-	filter := bson.M{"label_id": labelID, "domain": domain}
+	filter := bson.M{"label_id": labelID, "domain": domain, "project": project}
 	if key != "" {
 		filter["key"] = key
 		return findOneKey(ctx, filter)
@@ -202,7 +208,7 @@ func FindKVByLabelID(ctx context.Context, domain, labelID, key string) ([]*model
 //FindKV get kvs by key, labels
 //because labels has a a lot of combination,
 //you can use WithDepth(0) to return only one kv which's labels exactly match the criteria
-func FindKV(ctx context.Context, domain string, options ...FindOption) ([]*model.KVResponse, error) {
+func FindKV(ctx context.Context, domain string, project string, options ...FindOption) ([]*model.KVResponse, error) {
 	opts := FindOptions{}
 	for _, o := range options {
 		o(&opts)
@@ -213,8 +219,11 @@ func FindKV(ctx context.Context, domain string, options ...FindOption) ([]*model
 	if domain == "" {
 		return nil, db.ErrMissingDomain
 	}
+	if project == "" {
+		return nil, db.ErrMissingProject
+	}
 
-	cur, err := findKV(ctx, domain, opts)
+	cur, err := findKV(ctx, domain, project, opts)
 	if err != nil {
 		return nil, err
 	}
diff --git a/server/service/label/dao.go b/server/service/label/dao.go
index eeca3b5..66acbaa 100644
--- a/server/service/label/dao.go
+++ b/server/service/label/dao.go
@@ -20,6 +20,7 @@ package label
 import (
 	"context"
 	"fmt"
+
 	"github.com/apache/servicecomb-kie/pkg/model"
 	"github.com/apache/servicecomb-kie/server/db"
 	"github.com/go-mesh/openlogging"
@@ -28,15 +29,20 @@ import (
 	"go.mongodb.org/mongo-driver/mongo/options"
 )
 
+const (
+	defaultLabels = "default"
+)
+
 //CreateLabel create a new label
-func CreateLabel(ctx context.Context, domain string, labels map[string]string) (*model.LabelDoc, error) {
+func CreateLabel(ctx context.Context, domain string, labels map[string]string, project string) (*model.LabelDoc, error) {
 	c, err := db.GetClient()
 	if err != nil {
 		return nil, err
 	}
 	l := &model.LabelDoc{
-		Domain: domain,
-		Labels: labels,
+		Domain:  domain,
+		Labels:  labels,
+		Project: project,
 	}
 	collection := c.Database(db.Name).Collection(db.CollectionLabel)
 	res, err := collection.InsertOne(ctx, l)
@@ -48,21 +54,21 @@ func CreateLabel(ctx context.Context, domain string, labels map[string]string) (
 	return l, nil
 }
 
-//FindLabels find label doc by labels
+//FindLabels find label doc by labels and project, check if the project has certain labels
 //if map is empty. will return default labels doc which has no labels
-func FindLabels(ctx context.Context, domain string, labels map[string]string) (*model.LabelDoc, error) {
+func FindLabels(ctx context.Context, domain string, project string, labels map[string]string) (*model.LabelDoc, error) {
 	c, err := db.GetClient()
 	if err != nil {
 		return nil, err
 	}
 	collection := c.Database(db.Name).Collection(db.CollectionLabel)
 
-	filter := bson.M{"domain": domain}
+	filter := bson.M{"domain": domain, "project": project}
 	for k, v := range labels {
 		filter["labels."+k] = v
 	}
 	if len(labels) == 0 {
-		filter["labels"] = "default" //allow key without labels
+		filter["labels"] = defaultLabels //allow key without labels
 	}
 	cur, err := collection.Find(ctx, filter)
 	if err != nil {
diff --git a/server/service/label/service.go b/server/service/label/service.go
index 8b0bee7..745548f 100644
--- a/server/service/label/service.go
+++ b/server/service/label/service.go
@@ -20,14 +20,15 @@ package label
 import (
 	"context"
 	"fmt"
+
 	"github.com/apache/servicecomb-kie/server/db"
 	"github.com/go-mesh/openlogging"
 	"go.mongodb.org/mongo-driver/bson/primitive"
 )
 
-//Exist check label exists or not and return label ID
-func Exist(ctx context.Context, domain string, labels map[string]string) (primitive.ObjectID, error) {
-	l, err := FindLabels(ctx, domain, labels)
+//Exist check whether the project has certain label or not and return label ID
+func Exist(ctx context.Context, domain string, project string, labels map[string]string) (primitive.ObjectID, error) {
+	l, err := FindLabels(ctx, domain, project, labels)
 	if err != nil {
 		if err.Error() == context.DeadlineExceeded.Error() {
 			openlogging.Error("find label failed, dead line exceeded", openlogging.WithTags(openlogging.Tags{
diff --git a/swagger/servicecomb-kie.yaml b/swagger/servicecomb-kie.yaml
index 4b786d6..70988fc 100644
--- a/swagger/servicecomb-kie.yaml
+++ b/swagger/servicecomb-kie.yaml
@@ -4,7 +4,7 @@ info:
   version: ""
 basePath: /
 paths:
-  /v1/kv:
+  /v1/{project}/kie/kv:
     get:
       summary: search key values by labels combination
       operationId: SearchByLabels
@@ -13,7 +13,7 @@ paths:
           in: query
           description:
             "the combination format is {label_key}:{label_value}+{label_key}:{label_value}
-            for example: /v1/kv?q=app:mall&q=app:mall+service:cart that will query key
+            for example: /v1/test/kie/kv?q=app:mall&q=app:mall+service:cart that will query key
             values from 2 kinds of labels"
           type: string
       consumes:
@@ -27,11 +27,15 @@ paths:
             type: array
             items:
               $ref: "#/definitions/KVResponse"
-  /v1/kv/{key}:
+  /v1/{project}/kie/kv/{key}:
     get:
       summary: get key values by key and labels
       operationId: GetByKey
       parameters:
+        - name: project
+          in: path
+          required: true
+          type: string
         - name: key
           in: path
           required: true
@@ -62,6 +66,10 @@ paths:
       summary: create or update key value
       operationId: Put
       parameters:
+        - name: project
+          in: path
+          required: true
+          type: string
         - name: key
           in: path
           required: true
@@ -82,7 +90,7 @@ paths:
       responses:
         "200":
           description: "true"
-  /v1/kv/{kvID}:
+  /v1/{project}/kie/kv/{kvID}:
     delete:
       summary:
         Delete key by kvID and labelID,If the labelID is nil, query the collection
@@ -90,6 +98,10 @@ paths:
         you want better performance, you need to pass the labelID
       operationId: Delete
       parameters:
+        - name: project
+          in: path
+          required: true
+          type: string
         - name: key
           in: path
           required: true
@@ -101,6 +113,37 @@ paths:
           description: Failed,check url
         "500":
           description: Server error
+  /v1/{project}/kie/revision/{label_id}:
+    get:
+      summary: get history revisions by label id
+      operationId: GetRevisionsByLabelID
+      parameters:
+        - name: project
+          in: path
+          required: true
+          type: string
+        - name: label_id
+          in: path
+          required: true
+          type: string
+      consumes:
+        - application/json
+      produces:
+        - application/json
+      responses:
+        "200":
+          description: get history revisions success
+          schema:
+            type: array
+            items:
+              $ref: "#/definitions/LabelRevisionResponse"
+        "403":
+          description: label_id must not be empty
+        "404":
+          description: no revisions found
+        "500":
+          description: internal server error
+
 definitions:
   KVDoc:
     type: object
@@ -147,6 +190,23 @@ definitions:
         type: object
         additionalProperties:
           type: string
+  LabelRevisionResponse:
+    type: object
+    properties:
+      data:
+        type: array
+        items:
+          $ref: "#/definitions/KVDoc"
+      label_id:
+        type: string
+      labels:
+        type: object
+        additionalProperties:
+          type: string
+      revision:
+        type: integer
+        format: int32
+
   v1.KVBody:
     type: object
     properties: