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/07/13 09:36:27 UTC

[servicecomb-kie] branch master updated: SCB-1331 support mongodb TLS communication (#20)

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 913ee43  SCB-1331 support mongodb TLS communication (#20)
913ee43 is described below

commit 913ee43f6c4a8a2785e7650d9db658e8e564c411
Author: Shawn <xi...@gmail.com>
AuthorDate: Sat Jul 13 17:36:23 2019 +0800

    SCB-1331 support mongodb TLS communication (#20)
---
 .gitignore                             |   1 +
 README.md                              |   6 +-
 examples/dev/conf/lager.yaml           |   1 +
 examples/dev/kie-conf.yaml             |   7 +-
 go.mod                                 |  11 ++-
 pkg/common/common.go                   |   5 ++
 server/config/struct.go                |  10 +--
 server/dao/errors.go                   |  34 -------
 server/dao/kie_api.go                  |  29 ++++--
 server/dao/kv.go                       |  36 +++++++-
 server/dao/label.go                    |   2 +-
 server/dao/label_history.go            |   2 +-
 server/resource/v1/common.go           |   7 +-
 server/resource/v1/kv_resource.go      |  56 +++++++-----
 server/resource/v1/kv_resource_test.go |   2 +-
 swagger/servicecomb-kie.yaml           | 157 +++++++++++++++++++++++++++++++++
 16 files changed, 275 insertions(+), 91 deletions(-)

diff --git a/.gitignore b/.gitignore
index 8d99bb8..89c876c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ output
 *.iml
 .idea/
 **/*junit.xml
+**/*crt
 **/*.exe
 **/*.tgz
 vendor/**
diff --git a/README.md b/README.md
index 43ece75..df84789 100644
--- a/README.md
+++ b/README.md
@@ -81,15 +81,19 @@ it will launch 3 components
 To see how to build a local dev environment, check [here](examples/dev)
 
 ### Build
+this will build your own service image and binary in local
 ```bash
 cd build
-export VERSION=0.0.1
+export VERSION=0.0.1 #optional, it is latest by default
 ./build_server.sh
 ```
 
 this will generate a "servicecomb-kie-0.0.1-linux-amd64.tar" in "release" folder,
 and a docker image "servicecomb/kie:0.0.1"
 
+# API Doc
+swagger/servicecomb-kie.yaml
+
 ## Contact
 
 Bugs: [issues](https://issues.apache.org/jira/browse/SCB)
diff --git a/examples/dev/conf/lager.yaml b/examples/dev/conf/lager.yaml
new file mode 100644
index 0000000..419ae46
--- /dev/null
+++ b/examples/dev/conf/lager.yaml
@@ -0,0 +1 @@
+log_format_text: false
\ No newline at end of file
diff --git a/examples/dev/kie-conf.yaml b/examples/dev/kie-conf.yaml
index af99321..c9633fa 100644
--- a/examples/dev/kie-conf.yaml
+++ b/examples/dev/kie-conf.yaml
@@ -1,7 +1,8 @@
 db:
+  #uri: mongodb://rwuser:xxx@x.x.x.x:8635/test?authSource=admin
   uri: mongodb://kie:123@127.0.0.1:27017
   type: mongodb
   poolSize: 10
-  ssl: false
-  sslCA:
-  sslCert:
\ No newline at end of file
+  timeout:  5m
+  sslEnabled: false
+  rootCAFile: /opt/kie/ca.crt
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 77abed3..b76a7cd 100644
--- a/go.mod
+++ b/go.mod
@@ -2,18 +2,17 @@ module github.com/apache/servicecomb-kie
 
 require (
 	github.com/emicklei/go-restful v2.8.0+incompatible
-	github.com/go-chassis/foundation v0.0.0-20190516083152-b8b2476b6db7
-	github.com/go-chassis/go-archaius v0.18.0
-	github.com/go-chassis/go-chassis v1.5.0
+	github.com/go-chassis/foundation v0.0.0-20190621030543-c3b63f787f4c
+	github.com/go-chassis/go-archaius v0.19.0
+	github.com/go-chassis/go-chassis v1.5.1
 	github.com/go-chassis/paas-lager v1.0.2-0.20190328010332-cf506050ddb2
 	github.com/go-mesh/openlogging v1.0.1-0.20181205082104-3d418c478b2d
 	github.com/onsi/ginkgo v1.8.0
 	github.com/onsi/gomega v1.5.0
-	github.com/pkg/errors v0.8.0
-	github.com/stretchr/testify v1.2.2
+	github.com/stretchr/testify v1.3.0
 	github.com/urfave/cli v1.20.0
 	github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
 	github.com/xdg/stringprep v1.0.0 // indirect
-	go.mongodb.org/mongo-driver v1.0.0
+	go.mongodb.org/mongo-driver v1.0.3
 	gopkg.in/yaml.v2 v2.2.1
 )
diff --git a/pkg/common/common.go b/pkg/common/common.go
index f111bce..f6a2ead 100644
--- a/pkg/common/common.go
+++ b/pkg/common/common.go
@@ -28,3 +28,8 @@ const (
 	HeaderDepth  = "X-Depth"
 	HeaderTenant = "X-Domain-Name"
 )
+
+//ContentType
+const (
+	ContentTypeText = "application/text"
+)
diff --git a/server/config/struct.go b/server/config/struct.go
index 0e2cd55..d3ad4da 100644
--- a/server/config/struct.go
+++ b/server/config/struct.go
@@ -24,9 +24,9 @@ type Config struct {
 
 //DB is yaml file struct to set mongodb config
 type DB struct {
-	URI      string   `yaml:"uri"`
-	PoolSize int      `yaml:"poolSize"`
-	SSL      bool     `yaml:"ssl"`
-	CABundle []string `yaml:"sslCA"`
-	Cert     string   `yaml:"sslCert"`
+	URI        string `yaml:"uri"`
+	PoolSize   int    `yaml:"poolSize"`
+	SSLEnabled bool   `yaml:"sslEnabled"`
+	RootCA     string `yaml:"rootCAFile"`
+	Timeout    string `yaml:"timeout"`
 }
diff --git a/server/dao/errors.go b/server/dao/errors.go
deleted file mode 100644
index 349ca6d..0000000
--- a/server/dao/errors.go
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package dao
-
-import (
-	"errors"
-	"fmt"
-
-	"github.com/go-mesh/openlogging"
-)
-
-//ErrAction will wrap raw error to biz error and return
-//it record audit log for mongodb operation failure like find, insert, updateKey, deletion
-func ErrAction(action, filter interface{}, err error) error {
-	msg := fmt.Sprintf("can not [%s] [%v],err: %s", action, filter, err.Error())
-	openlogging.Error(msg)
-	return errors.New(msg)
-
-}
diff --git a/server/dao/kie_api.go b/server/dao/kie_api.go
index de65dcd..37e5fa9 100644
--- a/server/dao/kie_api.go
+++ b/server/dao/kie_api.go
@@ -63,7 +63,7 @@ func (s *MongodbService) CreateOrUpdate(ctx context.Context, domain string, kv *
 			"default": "default",
 		}
 	}
-	ctx, _ = context.WithTimeout(ctx, DefaultTimeout)
+	ctx, _ = context.WithTimeout(ctx, s.timeout)
 	//check labels exits or not
 	labelID, err := s.LabelsExist(ctx, domain, kv.Labels)
 	var l *model.LabelDoc
@@ -114,7 +114,7 @@ func (s *MongodbService) CreateOrUpdate(ctx context.Context, domain string, kv *
 //if map is empty. will return default labels doc which has no labels
 func (s *MongodbService) FindLabels(ctx context.Context, domain string, labels map[string]string) (*model.LabelDoc, error) {
 	collection := s.c.Database(DB).Collection(CollectionLabel)
-	ctx, _ = context.WithTimeout(context.Background(), DefaultTimeout)
+	ctx, _ = context.WithTimeout(context.Background(), s.timeout)
 	filter := bson.M{"domain": domain}
 	for k, v := range labels {
 		filter["labels."+k] = v
@@ -125,7 +125,10 @@ func (s *MongodbService) FindLabels(ctx context.Context, domain string, labels m
 	cur, err := collection.Find(ctx, filter)
 	if err != nil {
 		if err.Error() == context.DeadlineExceeded.Error() {
-			return nil, ErrAction("find label", filter, fmt.Errorf("can not reach mongodb in %s", s.timeout))
+			openlogging.Error("find label failed, dead line exceeded", openlogging.WithTags(openlogging.Tags{
+				"timeout": s.timeout,
+			}))
+			return nil, fmt.Errorf("can not reach mongodb in %s", s.timeout)
 		}
 		return nil, err
 	}
@@ -158,7 +161,10 @@ func (s *MongodbService) findKeys(ctx context.Context, filter bson.M, withoutLab
 	cur, err := collection.Find(ctx, filter)
 	if err != nil {
 		if err.Error() == context.DeadlineExceeded.Error() {
-			return nil, ErrAction("find", filter, fmt.Errorf("can not reach mongodb in %s", s.timeout))
+			openlogging.Error("find kvs failed, dead line exceeded", openlogging.WithTags(openlogging.Tags{
+				"timeout": s.timeout,
+			}))
+			return nil, fmt.Errorf("can not reach mongodb in %s", s.timeout)
 		}
 		return nil, err
 	}
@@ -190,7 +196,7 @@ func (s *MongodbService) findKeys(ctx context.Context, filter bson.M, withoutLab
 //key can be empty, then it will return all key values
 //if key is given, will return 0-1 key value
 func (s *MongodbService) FindKVByLabelID(ctx context.Context, domain, labelID, key string) ([]*model.KVDoc, error) {
-	ctx, _ = context.WithTimeout(context.Background(), DefaultTimeout)
+	ctx, _ = context.WithTimeout(context.Background(), s.timeout)
 	filter := bson.M{"label_id": labelID, "domain": domain}
 	if key != "" {
 		filter["key"] = key
@@ -284,7 +290,7 @@ func (s *MongodbService) FindKV(ctx context.Context, domain string, options ...F
 //domain=tenant
 //1.delete kv;2.add history
 func (s *MongodbService) Delete(kvID string, labelID string, domain string) error {
-	ctx, _ := context.WithTimeout(context.Background(), DefaultTimeout)
+	ctx, _ := context.WithTimeout(context.Background(), s.timeout)
 	if domain == "" {
 		return ErrMissingDomain
 	}
@@ -354,17 +360,22 @@ func NewMongoService(opts Options) (*MongodbService, error) {
 func getClient(opts Options) (*mongo.Client, error) {
 	if client == nil {
 		var err error
-		client, err = mongo.NewClient(options.Client().ApplyURI(opts.URI))
+		clientOps := []*options.ClientOptions{options.Client().ApplyURI(opts.URI)}
+		if opts.TLS != nil {
+			clientOps = append(clientOps, options.Client().SetTLSConfig(opts.TLS))
+			openlogging.Info("enabled ssl communication to mongodb")
+		}
+		client, err = mongo.NewClient(clientOps...)
 		if err != nil {
 			return nil, err
 		}
-		openlogging.Info("connecting to " + opts.URI)
+		openlogging.Info("DB connecting")
 		ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
 		err = client.Connect(ctx)
 		if err != nil {
 			return nil, err
 		}
-		openlogging.Info("connected to " + opts.URI)
+		openlogging.Info("DB connected")
 	}
 	return client, nil
 }
diff --git a/server/dao/kv.go b/server/dao/kv.go
index e3dcad1..f2065a5 100644
--- a/server/dao/kv.go
+++ b/server/dao/kv.go
@@ -21,6 +21,7 @@ package dao
 import (
 	"context"
 	"crypto/tls"
+	"crypto/x509"
 	"errors"
 	"fmt"
 	"github.com/apache/servicecomb-kie/pkg/model"
@@ -29,6 +30,7 @@ import (
 	"go.mongodb.org/mongo-driver/bson"
 	"go.mongodb.org/mongo-driver/bson/primitive"
 	"go.mongodb.org/mongo-driver/mongo"
+	"io/ioutil"
 	"time"
 )
 
@@ -42,6 +44,7 @@ var (
 	ErrRevisionNotExist       = errors.New("label revision not exist")
 	ErrKVIDIsNil              = errors.New("kvID id is nil")
 	ErrKvIDAndLabelIDNotMatch = errors.New("kvID and labelID do not match")
+	ErrRootCAMissing          = errors.New("rootCAFile is empty in config file")
 )
 
 //Options mongodb options
@@ -56,13 +59,35 @@ type Options struct {
 //NewKVService create a kv service
 //TODO, multiple config server
 func NewKVService() (*MongodbService, error) {
+	var d time.Duration
+	var err error
+	if config.GetDB().Timeout != "" {
+		d, err = time.ParseDuration(config.GetDB().Timeout)
+		if err != nil {
+			return nil, errors.New("timeout setting invalid:" + config.GetDB().Timeout)
+		}
+	}
 	opts := Options{
 		URI:      config.GetDB().URI,
 		PoolSize: config.GetDB().PoolSize,
-		SSL:      config.GetDB().SSL,
+		SSL:      config.GetDB().SSLEnabled,
+		Timeout:  d,
 	}
 	if opts.SSL {
-		//TODO tls config
+		if config.GetDB().RootCA == "" {
+			return nil, ErrRootCAMissing
+		}
+		pool := x509.NewCertPool()
+		caCert, err := ioutil.ReadFile(config.GetDB().RootCA)
+		if err != nil {
+			return nil, fmt.Errorf("read ca cert file %s failed", caCert)
+		}
+		pool.AppendCertsFromPEM(caCert)
+		opts.TLS = &tls.Config{
+			RootCAs:            pool,
+			InsecureSkipVerify: true,
+		}
+
 	}
 	return NewMongoService(opts)
 }
@@ -111,7 +136,7 @@ func (s *MongodbService) KVExist(ctx context.Context, domain, key string, option
 
 func (s *MongodbService) findKV(ctx context.Context, domain string, opts FindOptions) (*mongo.Cursor, error) {
 	collection := s.c.Database(DB).Collection(CollectionKV)
-	ctx, _ = context.WithTimeout(ctx, DefaultTimeout)
+	ctx, _ = context.WithTimeout(ctx, s.timeout)
 	filter := bson.M{"domain": domain}
 	if opts.Key != "" {
 		filter["key"] = opts.Key
@@ -123,7 +148,10 @@ func (s *MongodbService) findKV(ctx context.Context, domain string, opts FindOpt
 	cur, err := collection.Find(ctx, filter)
 	if err != nil {
 		if err.Error() == context.DeadlineExceeded.Error() {
-			return nil, ErrAction("find", filter, fmt.Errorf("can not reach mongodb in %s", s.timeout))
+			openlogging.Error("find kv failed, deadline exceeded", openlogging.WithTags(openlogging.Tags{
+				"timeout": s.timeout,
+			}))
+			return nil, fmt.Errorf("can not reach mongodb in %s", s.timeout)
 		}
 		return nil, err
 	}
diff --git a/server/dao/label.go b/server/dao/label.go
index 742c3dc..f80088f 100644
--- a/server/dao/label.go
+++ b/server/dao/label.go
@@ -42,7 +42,7 @@ func (s *MongodbService) createLabel(ctx context.Context, domain string, labels
 }
 func (s *MongodbService) findOneLabels(ctx context.Context, filter bson.M) (*model.LabelDoc, error) {
 	collection := s.c.Database(DB).Collection(CollectionLabel)
-	ctx, _ = context.WithTimeout(context.Background(), DefaultTimeout)
+	ctx, _ = context.WithTimeout(context.Background(), s.timeout)
 	sr := collection.FindOne(ctx, filter)
 	if sr.Err() != nil {
 		return nil, sr.Err()
diff --git a/server/dao/label_history.go b/server/dao/label_history.go
index c796e7b..08f8766 100644
--- a/server/dao/label_history.go
+++ b/server/dao/label_history.go
@@ -29,7 +29,7 @@ import (
 
 func (s *MongodbService) getLatestLabel(ctx context.Context, labelID string) (*model.LabelRevisionDoc, error) {
 	collection := s.c.Database(DB).Collection(CollectionLabelRevision)
-	ctx, _ = context.WithTimeout(ctx, DefaultTimeout)
+	ctx, _ = context.WithTimeout(ctx, s.timeout)
 
 	filter := bson.M{"label_id": labelID}
 
diff --git a/server/resource/v1/common.go b/server/resource/v1/common.go
index f96c11c..db40b4a 100644
--- a/server/resource/v1/common.go
+++ b/server/resource/v1/common.go
@@ -19,13 +19,13 @@ package v1
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"github.com/apache/servicecomb-kie/pkg/common"
 	"github.com/apache/servicecomb-kie/pkg/model"
 	goRestful "github.com/emicklei/go-restful"
 	"github.com/go-chassis/go-chassis/server/restful"
 	"github.com/go-mesh/openlogging"
-	"github.com/pkg/errors"
 	"strconv"
 	"strings"
 )
@@ -80,14 +80,17 @@ func ReadLabelCombinations(req *goRestful.Request) ([]map[string]string, error)
 	if len(labelCombinations) == 0 {
 		return []map[string]string{{"default": "default"}}, nil
 	}
+
 	return labelCombinations, nil
 }
 
 //WriteErrResponse write error message to client
-func WriteErrResponse(context *restful.Context, status int, msg string) {
+func WriteErrResponse(context *restful.Context, status int, msg, contentType string) {
 	context.WriteHeader(status)
 	b, _ := json.MarshalIndent(&ErrorMsg{Msg: msg}, "", " ")
+	context.ReadRestfulResponse().AddHeader(goRestful.HEADER_ContentType, contentType)
 	context.Write(b)
+
 }
 
 //ErrLog record error
diff --git a/server/resource/v1/kv_resource.go b/server/resource/v1/kv_resource.go
index d8b2d50..443c1a5 100644
--- a/server/resource/v1/kv_resource.go
+++ b/server/resource/v1/kv_resource.go
@@ -20,6 +20,7 @@ package v1
 
 import (
 	"encoding/json"
+	"github.com/apache/servicecomb-kie/pkg/common"
 	"github.com/apache/servicecomb-kie/pkg/model"
 	"github.com/apache/servicecomb-kie/server/dao"
 	goRestful "github.com/emicklei/go-restful"
@@ -39,23 +40,23 @@ func (r *KVResource) Put(context *restful.Context) {
 	kv := new(model.KVDoc)
 	decoder := json.NewDecoder(context.ReadRequest().Body)
 	if err = decoder.Decode(kv); err != nil {
-		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
+		WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
 		return
 	}
 	domain := ReadDomain(context)
 	if domain == nil {
-		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty)
+		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
 	}
 	kv.Key = key
 	s, err := dao.NewKVService()
 	if err != nil {
-		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
+		WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
 		return
 	}
 	kv, err = s.CreateOrUpdate(context.Ctx, domain.(string), kv)
 	if err != nil {
 		ErrLog("put", kv, err)
-		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
+		WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
 		return
 	}
 	InfoLog("put", kv)
@@ -69,40 +70,40 @@ func (r *KVResource) GetByKey(context *restful.Context) {
 	var err error
 	key := context.ReadPathParameter("key")
 	if key == "" {
-		WriteErrResponse(context, http.StatusForbidden, "key must not be empty")
+		WriteErrResponse(context, http.StatusForbidden, "key must not be empty", common.ContentTypeText)
 		return
 	}
 	values := context.ReadRequest().URL.Query()
 	labels := make(map[string]string, len(values))
 	for k, v := range values {
 		if len(v) != 1 {
-			WriteErrResponse(context, http.StatusBadRequest, MsgIllegalLabels)
+			WriteErrResponse(context, http.StatusBadRequest, MsgIllegalLabels, common.ContentTypeText)
 			return
 		}
 		labels[k] = v[0]
 	}
 	s, err := dao.NewKVService()
 	if err != nil {
-		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
+		WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
 		return
 	}
 	domain := ReadDomain(context)
 	if domain == nil {
-		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty)
+		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
 		return
 	}
 	d, err := ReadFindDepth(context)
 	if err != nil {
-		WriteErrResponse(context, http.StatusBadRequest, MsgIllegalDepth)
+		WriteErrResponse(context, http.StatusBadRequest, MsgIllegalDepth, common.ContentTypeText)
 		return
 	}
 	kvs, err := s.FindKV(context.Ctx, domain.(string), dao.WithKey(key), dao.WithLabels(labels), dao.WithDepth(d))
 	if err != nil {
 		if err == dao.ErrKeyNotExists {
-			WriteErrResponse(context, http.StatusNotFound, err.Error())
+			WriteErrResponse(context, http.StatusNotFound, err.Error(), common.ContentTypeText)
 			return
 		}
-		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
+		WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
 		return
 	}
 	err = context.WriteHeaderAndJSON(http.StatusOK, kvs, goRestful.MIME_JSON)
@@ -117,34 +118,41 @@ func (r *KVResource) SearchByLabels(context *restful.Context) {
 	var err error
 	labelCombinations, err := ReadLabelCombinations(context.ReadRestfulRequest())
 	if err != nil {
-		WriteErrResponse(context, http.StatusBadRequest, err.Error())
+		WriteErrResponse(context, http.StatusBadRequest, err.Error(), common.ContentTypeText)
 		return
 	}
 	s, err := dao.NewKVService()
 	if err != nil {
-		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
+		WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
 		return
 	}
 	domain := ReadDomain(context)
 	if domain == nil {
-		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty)
+		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
 		return
 	}
 	var kvs []*model.KVResponse
 	for _, labels := range labelCombinations {
+		openlogging.Debug("find by combination", openlogging.WithTags(openlogging.Tags{
+			"q": labels,
+		}))
 		result, err := s.FindKV(context.Ctx, domain.(string), dao.WithLabels(labels))
 		if err != nil {
 			if err == dao.ErrKeyNotExists {
 				continue
+			} else {
+				openlogging.Error("can not find by labels", openlogging.WithTags(openlogging.Tags{
+					"err": err.Error(),
+				}))
+				WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
+				return
 			}
-			WriteErrResponse(context, http.StatusInternalServerError, err.Error())
-			return
 		}
 		kvs = append(kvs, result...)
 
 	}
 	if len(kvs) == 0 {
-		WriteErrResponse(context, http.StatusNotFound, "no kv found")
+		WriteErrResponse(context, http.StatusNotFound, "no kv found", common.ContentTypeText)
 		return
 	}
 
@@ -159,17 +167,17 @@ func (r *KVResource) SearchByLabels(context *restful.Context) {
 func (r *KVResource) Delete(context *restful.Context) {
 	domain := ReadDomain(context)
 	if domain == nil {
-		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty)
+		WriteErrResponse(context, http.StatusInternalServerError, MsgDomainMustNotBeEmpty, common.ContentTypeText)
 	}
 	kvID := context.ReadPathParameter("kvID")
 	if kvID == "" {
-		WriteErrResponse(context, http.StatusBadRequest, ErrKvIDMustNotEmpty)
+		WriteErrResponse(context, http.StatusBadRequest, ErrKvIDMustNotEmpty, common.ContentTypeText)
 		return
 	}
 	labelID := context.ReadQueryParameter("labelID")
 	s, err := dao.NewKVService()
 	if err != nil {
-		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
+		WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
 		return
 	}
 	err = s.Delete(kvID, labelID, domain.(string))
@@ -179,7 +187,7 @@ func (r *KVResource) Delete(context *restful.Context) {
 			"labelID": labelID,
 			"error":   err.Error(),
 		}))
-		WriteErrResponse(context, http.StatusInternalServerError, err.Error())
+		WriteErrResponse(context, http.StatusInternalServerError, err.Error(), common.ContentTypeText)
 		return
 	}
 	context.WriteHeader(http.StatusNoContent)
@@ -209,7 +217,7 @@ func (r *KVResource) URLPatterns() []restful.Route {
 			},
 			Consumes: []string{goRestful.MIME_JSON},
 			Produces: []string{goRestful.MIME_JSON},
-			Read:     &KVBody{},
+			Read:     KVBody{},
 		}, {
 			Method:           http.MethodGet,
 			Path:             "/v1/kv/{key}",
@@ -222,7 +230,7 @@ func (r *KVResource) URLPatterns() []restful.Route {
 				{
 					Code:    http.StatusOK,
 					Message: "get key value success",
-					Model:   []*model.KVResponse{},
+					Model:   []model.KVResponse{},
 				},
 			},
 			Consumes: []string{goRestful.MIME_JSON},
@@ -240,7 +248,7 @@ func (r *KVResource) URLPatterns() []restful.Route {
 				{
 					Code:    http.StatusOK,
 					Message: "get key value success",
-					Model:   []*model.KVResponse{},
+					Model:   []model.KVResponse{},
 				},
 			},
 			Consumes: []string{goRestful.MIME_JSON},
diff --git a/server/resource/v1/kv_resource_test.go b/server/resource/v1/kv_resource_test.go
index 8119ef0..445ef6f 100644
--- a/server/resource/v1/kv_resource_test.go
+++ b/server/resource/v1/kv_resource_test.go
@@ -36,8 +36,8 @@ var _ = Describe("v1 kv resource", func() {
 	config.Configurations = &config.Config{
 		DB: config.DB{},
 	}
+	config.Configurations.DB.URI = "mongodb://kie:123@127.0.0.1:27017"
 	Describe("put kv", func() {
-		config.Configurations.DB.URI = "mongodb://kie:123@127.0.0.1:27017"
 		Context("valid param", func() {
 			kv := &model.KVDoc{
 				Value:  "1s",
diff --git a/swagger/servicecomb-kie.yaml b/swagger/servicecomb-kie.yaml
new file mode 100644
index 0000000..4e7685b
--- /dev/null
+++ b/swagger/servicecomb-kie.yaml
@@ -0,0 +1,157 @@
+swagger: "2.0"
+info:
+  title: ""
+  version: ""
+basePath: /
+paths:
+  /v1/kv:
+    get:
+      summary: search key values by labels combination
+      operationId: SearchByLabels
+      parameters:
+      - name: q
+        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
+          values from 2 kinds of labels'
+        type: string
+      consumes:
+      - application/json
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: get key value success
+          schema:
+            type: array
+            items:
+              $ref: '#/definitions/KVResponse'
+  /v1/kv/{key}:
+    get:
+      summary: get key values by key and labels
+      operationId: GetByKey
+      parameters:
+      - name: key
+        in: path
+        required: true
+        type: string
+      - name: X-Depth
+        in: header
+        description: integer, default is 1, if you set match policy, you can set,depth
+          to decide label number
+        type: string
+      - name: body
+        in: body
+        required: true
+        schema:
+          $ref: '#/definitions/*v1.KVBody'
+      consumes:
+      - application/json
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: get key value success
+          schema:
+            type: array
+            items:
+              $ref: '#/definitions/KVResponse'
+    put:
+      summary: create or update key value
+      operationId: Put
+      parameters:
+      - name: key
+        in: path
+        required: true
+        type: string
+      - name: X-Realm
+        in: header
+        description: set kv to heterogeneous config server, not implement yet
+        type: string
+      - name: body
+        in: body
+        required: true
+        schema:
+          $ref: '#/definitions/v1.KVBody'
+      consumes:
+      - application/json
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: "true"
+  /v1/kv/{kvID}:
+    delete:
+      summary: 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
+      operationId: Delete
+      parameters:
+      - name: key
+        in: path
+        required: true
+        type: string
+      responses:
+        "204":
+          description: Delete success
+        "400":
+          description: Failed,check url
+        "500":
+          description: Server error
+definitions:
+  KVDoc:
+    type: object
+    properties:
+      _id:
+        type: array
+        items:
+          type: integer
+          format: byte
+      check:
+        type: string
+      domain:
+        type: string
+      key:
+        type: string
+      label_id:
+        type: string
+      labels:
+        type: object
+        additionalProperties:
+          type: string
+      revision:
+        type: integer
+        format: int32
+      value:
+        type: string
+      value_type:
+        type: string
+  KVResponse:
+    type: object
+    properties:
+      data:
+        type: array
+        items:
+          $ref: '#/definitions/KVDoc'
+      label:
+        $ref: '#/definitions/LabelDocResponse'
+  LabelDocResponse:
+    type: object
+    properties:
+      label_id:
+        type: string
+      labels:
+        type: object
+        additionalProperties:
+          type: string
+  v1.KVBody:
+    type: object
+    properties:
+      labels:
+        type: object
+        additionalProperties:
+          type: string
+      value:
+        type: string
+      valueType:
+        type: string